From e4e66a03d7a9bcfad18bf4bf86c314a35ff68cfe Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Mon, 19 Jan 2015 11:20:00 +0200 Subject: [PATCH] Creates initial version of xmpp module. --- app.js | 721 +-- estos_log.js | 17 - index.html | 16 +- keyboard_shortcut.js | 8 +- libs/modules/API.bundle.js | 7 +- libs/modules/RTC.bundle.js | 23 +- libs/modules/UI.bundle.js | 881 ++- libs/modules/connectionquality.bundle.js | 6 +- libs/modules/desktopsharing.bundle.js | 5 +- libs/modules/simulcast.bundle.js | 106 +- libs/modules/statistics.bundle.js | 35 +- libs/modules/xmpp.bundle.js | 5082 +++++++++++++++++ libs/rayo.js | 103 - libs/strophe/strophe.jingle.js | 327 -- libs/strophe/strophe.util.js | 41 - moderatemuc.js | 56 - modules/API/API.js | 4 +- modules/RTC/DataChannels.js | 4 +- modules/RTC/RTC.js | 13 +- modules/UI/UI.js | 260 +- modules/UI/audio_levels/AudioLevels.js | 8 +- modules/UI/authentication/Authentication.js | 84 + modules/UI/avatar/Avatar.js | 10 +- modules/UI/etherpad/Etherpad.js | 5 +- modules/UI/prezi/Prezi.js | 16 +- modules/UI/side_pannels/SidePanelToggler.js | 5 +- modules/UI/side_pannels/chat/Chat.js | 9 +- modules/UI/side_pannels/chat/Commands.js | 2 +- .../side_pannels/contactlist/ContactList.js | 31 +- .../UI/side_pannels/settings/SettingsMenu.js | 20 +- modules/UI/toolbars/Toolbar.js | 92 +- modules/UI/toolbars/ToolbarToggler.js | 2 +- modules/UI/util/UIUtil.js | 7 + modules/UI/videolayout/VideoLayout.js | 278 +- .../connectionquality/connectionquality.js | 3 +- modules/desktopsharing/desktopsharing.js | 2 +- modules/simulcast/SimulcastReceiver.js | 103 +- modules/statistics/RTPStatsCollector.js | 27 +- modules/statistics/statistics.js | 17 +- .../xmpp/JingleSession.js | 266 +- .../xmpp/SDP.js | 513 +- modules/xmpp/SDPDiffer.js | 165 + modules/xmpp/SDPUtil.js | 349 ++ .../xmpp/TraceablePeerConnection.js | 2 + moderator.js => modules/xmpp/moderator.js | 141 +- modules/xmpp/recording.js | 152 + modules/xmpp/strophe.emuc.js | 607 ++ modules/xmpp/strophe.jingle.js | 334 ++ modules/xmpp/strophe.logger.js | 20 + modules/xmpp/strophe.moderate.js | 58 + modules/xmpp/strophe.rayo.js | 95 + modules/xmpp/strophe.util.js | 42 + modules/xmpp/xmpp.js | 422 ++ muc.js | 548 -- recording.js | 167 - service/xmpp/XMPPEvents.js | 14 + 56 files changed, 8946 insertions(+), 3385 deletions(-) delete mode 100644 estos_log.js create mode 100644 libs/modules/xmpp.bundle.js delete mode 100644 libs/rayo.js delete mode 100644 libs/strophe/strophe.jingle.js delete mode 100644 libs/strophe/strophe.util.js delete mode 100644 moderatemuc.js create mode 100644 modules/UI/authentication/Authentication.js rename libs/strophe/strophe.jingle.session.js => modules/xmpp/JingleSession.js (83%) rename libs/strophe/strophe.jingle.sdp.js => modules/xmpp/SDP.js (55%) create mode 100644 modules/xmpp/SDPDiffer.js create mode 100644 modules/xmpp/SDPUtil.js rename libs/strophe/strophe.jingle.adapter.js => modules/xmpp/TraceablePeerConnection.js (99%) rename moderator.js => modules/xmpp/moderator.js (70%) create mode 100644 modules/xmpp/recording.js create mode 100644 modules/xmpp/strophe.emuc.js create mode 100644 modules/xmpp/strophe.jingle.js create mode 100644 modules/xmpp/strophe.logger.js create mode 100644 modules/xmpp/strophe.moderate.js create mode 100644 modules/xmpp/strophe.rayo.js create mode 100644 modules/xmpp/strophe.util.js create mode 100644 modules/xmpp/xmpp.js delete mode 100644 muc.js delete mode 100644 recording.js create mode 100644 service/xmpp/XMPPEvents.js diff --git a/app.js b/app.js index 93c88eb45..5cee715dc 100644 --- a/app.js +++ b/app.js @@ -1,17 +1,8 @@ /* jshint -W117 */ /* application specific logic */ -var connection = null; -var authenticatedUser = false; -/* Initial "authentication required" dialog */ -var authDialog = null; -/* Loop retry ID that wits for other user to create the room */ -var authRetryId = null; -var activecall = null; var nickname = null; var focusMucJid = null; -var roomName = null; var ssrc2jid = {}; -var bridgeIsDown = false; //TODO: this array must be removed when firefox implement multistream support var notReceivedSSRCs = []; @@ -27,674 +18,12 @@ var ssrc2videoType = {}; * @type {String} */ var focusedVideoInfo = null; -var mutedAudios = {}; -/** - * Remembers if we were muted by the focus. - * @type {boolean} - */ -var forceMuted = false; -/** - * Indicates if we have muted our audio before the conference has started. - * @type {boolean} - */ -var preMuted = false; - -var localVideoSrc = null; -var flipXLocalVideo = true; -var isFullScreen = false; -var currentVideoWidth = null; -var currentVideoHeight = null; - -var sessionTerminated = false; function init() { - - RTC.addStreamListener(maybeDoJoin, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); RTC.start(); + xmpp.start(UI.getCreadentials); - var jid = document.getElementById('jid').value || config.hosts.anonymousdomain || config.hosts.domain || window.location.hostname; - connect(jid); -} - -function connect(jid, password) { - connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind'); - - var settings = UI.getSettings(); - var email = settings.email; - var displayName = settings.displayName; - if(email) { - connection.emuc.addEmailToPresence(email); - } else { - connection.emuc.addUserIdToPresence(settings.uid); - } - if(displayName) { - connection.emuc.addDisplayNameToPresence(displayName); - } - - if (connection.disco) { - // for chrome, add multistream cap - } - connection.jingle.pc_constraints = RTC.getPCConstraints(); - if (config.useIPv6) { - // https://code.google.com/p/webrtc/issues/detail?id=2828 - if (!connection.jingle.pc_constraints.optional) connection.jingle.pc_constraints.optional = []; - connection.jingle.pc_constraints.optional.push({googIPv6: true}); - } - - if(!password) - password = document.getElementById('password').value; - - var anonymousConnectionFailed = false; - connection.connect(jid, password, function (status, msg) { - console.log('Strophe status changed to', Strophe.getStatusString(status)); - if (status === Strophe.Status.CONNECTED) { - if (config.useStunTurn) { - connection.jingle.getStunAndTurnCredentials(); - } - document.getElementById('connect').disabled = true; - - console.info("My Jabber ID: " + connection.jid); - - if(password) - authenticatedUser = true; - maybeDoJoin(); - } else if (status === Strophe.Status.CONNFAIL) { - if(msg === 'x-strophe-bad-non-anon-jid') { - anonymousConnectionFailed = true; - } - } else if (status === Strophe.Status.DISCONNECTED) { - if(anonymousConnectionFailed) { - // prompt user for username and password - $(document).trigger('passwordrequired.main'); - } - } else if (status === Strophe.Status.AUTHFAIL) { - // wrong password or username, prompt user - $(document).trigger('passwordrequired.main'); - - } - }); -} - - - -function maybeDoJoin() { - if (connection && connection.connected && Strophe.getResourceFromJid(connection.jid) // .connected is true while connecting? - && (RTC.localAudio || RTC.localVideo)) { - doJoin(); - } -} - -function doJoin() { - if (!roomName) { - UI.generateRoomName(); - } - - Moderator.allocateConferenceFocus( - roomName, doJoinAfterFocus); -} - -function doJoinAfterFocus() { - - // Close authentication dialog if opened - if (authDialog) { - UI.messageHandler.closeDialog(); - authDialog = null; - } - // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice - if (authRetryId) { - window.clearTimeout(authRetryId); - authRetryId = null; - } - - var roomjid; - roomjid = roomName; - - if (config.useNicks) { - var nick = window.prompt('Your nickname (optional)'); - if (nick) { - roomjid += '/' + nick; - } else { - roomjid += '/' + Strophe.getNodeFromJid(connection.jid); - } - } else { - - var tmpJid = Strophe.getNodeFromJid(connection.jid); - - if(!authenticatedUser) - tmpJid = tmpJid.substr(0, 8); - - roomjid += '/' + tmpJid; - } - connection.emuc.doJoin(roomjid); -} - -function waitForRemoteVideo(selector, ssrc, stream, jid) { - // XXX(gp) so, every call to this function is *always* preceded by a call - // to the RTC.attachMediaStream() function but that call is *not* followed - // by an update to the videoSrcToSsrc map! - // - // The above way of doing things results in video SRCs that don't correspond - // to any SSRC for a short period of time (to be more precise, for as long - // the waitForRemoteVideo takes to complete). This causes problems (see - // bellow). - // - // I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream() - // a second time in here and only then update the videoSrcToSsrc map? Why - // not simply update the videoSrcToSsrc map when the RTC.attachMediaStream() - // is called the first time? I actually do that in the lastN changed event - // handler because the "orphan" video SRC is causing troubles there. The - // purpose of this method would then be to fire the "videoactive.jingle". - // - // Food for though I guess :-) - - if (selector.removed || !selector.parent().is(":visible")) { - console.warn("Media removed before had started", selector); - return; - } - - if (stream.id === 'mixedmslabel') return; - - if (selector[0].currentTime > 0) { - var videoStream = simulcast.getReceivingVideoStream(stream); - RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF? - - // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type - // in order to get rid of too many maps - if (ssrc && jid) { - jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc; - } else { - console.warn("No ssrc given for jid", jid); - } - - $(document).trigger('videoactive.jingle', [selector]); - } else { - setTimeout(function () { - waitForRemoteVideo(selector, ssrc, stream, jid); - }, 250); - } -} - -$(document).bind('remotestreamadded.jingle', function (event, data, sid) { - waitForPresence(data, sid); -}); - -function waitForPresence(data, sid) { - var sess = connection.jingle.sessions[sid]; - - var thessrc; - - // look up an associated JID for a stream id - if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) { - // look only at a=ssrc: and _not_ at a=ssrc-group: lines - - var ssrclines - = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc:'); - ssrclines = ssrclines.filter(function (line) { - // NOTE(gp) previously we filtered on the mslabel, but that property - // is not always present. - // return line.indexOf('mslabel:' + data.stream.label) !== -1; - - return ((line.indexOf('msid:' + data.stream.id) !== -1)); - }); - if (ssrclines.length) { - thessrc = ssrclines[0].substring(7).split(' ')[0]; - - // We signal our streams (through Jingle to the focus) before we set - // our presence (through which peers associate remote streams to - // jids). So, it might arrive that a remote stream is added but - // ssrc2jid is not yet updated and thus data.peerjid cannot be - // successfully set. Here we wait for up to a second for the - // presence to arrive. - - if (!ssrc2jid[thessrc]) { - // TODO(gp) limit wait duration to 1 sec. - setTimeout(function(d, s) { - return function() { - waitForPresence(d, s); - } - }(data, sid), 250); - return; - } - - // ok to overwrite the one from focus? might save work in colibri.js - console.log('associated jid', ssrc2jid[thessrc], data.peerjid); - if (ssrc2jid[thessrc]) { - data.peerjid = ssrc2jid[thessrc]; - } - } - } - - //TODO: this code should be removed when firefox implement multistream support - if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX) - { - if((notReceivedSSRCs.length == 0) || - !ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]]) - { - // TODO(gp) limit wait duration to 1 sec. - setTimeout(function(d, s) { - return function() { - waitForPresence(d, s); - } - }(data, sid), 250); - return; - } - - thessrc = notReceivedSSRCs.pop(); - if (ssrc2jid[thessrc]) { - data.peerjid = ssrc2jid[thessrc]; - } - } - - RTC.createRemoteStream(data, sid, thessrc); - - var isVideo = data.stream.getVideoTracks().length > 0; - // an attempt to work around https://github.com/jitsi/jitmeet/issues/32 - if (isVideo && - data.peerjid && sess.peerjid === data.peerjid && - data.stream.getVideoTracks().length === 0 && - RTC.localVideo.getTracks().length > 0) { - // - window.setTimeout(function () { - sendKeyframe(sess.peerconnection); - }, 3000); - } -} - -// an attempt to work around https://github.com/jitsi/jitmeet/issues/32 -function sendKeyframe(pc) { - console.log('sendkeyframe', pc.iceConnectionState); - if (pc.iceConnectionState !== 'connected') return; // safe... - pc.setRemoteDescription( - pc.remoteDescription, - function () { - pc.createAnswer( - function (modifiedAnswer) { - pc.setLocalDescription( - modifiedAnswer, - function () { - // noop - }, - function (error) { - console.log('triggerKeyframe setLocalDescription failed', error); - UI.messageHandler.showError(); - } - ); - }, - function (error) { - console.log('triggerKeyframe createAnswer failed', error); - UI.messageHandler.showError(); - } - ); - }, - function (error) { - console.log('triggerKeyframe setRemoteDescription failed', error); - UI.messageHandler.showError(); - } - ); -} - -// Really mute video, i.e. dont even send black frames -function muteVideo(pc, unmute) { - // FIXME: this probably needs another of those lovely state safeguards... - // which checks for iceconn == connected and sigstate == stable - pc.setRemoteDescription(pc.remoteDescription, - function () { - pc.createAnswer( - function (answer) { - var sdp = new SDP(answer.sdp); - if (sdp.media.length > 1) { - if (unmute) - sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv'); - else - sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly'); - sdp.raw = sdp.session + sdp.media.join(''); - answer.sdp = sdp.raw; - } - pc.setLocalDescription(answer, - function () { - console.log('mute SLD ok'); - }, - function (error) { - console.log('mute SLD error'); - UI.messageHandler.showError('Error', - 'Oops! Something went wrong and we failed to ' + - 'mute! (SLD Failure)'); - } - ); - }, - function (error) { - console.log(error); - UI.messageHandler.showError(); - } - ); - }, - function (error) { - console.log('muteVideo SRD error'); - UI.messageHandler.showError('Error', - 'Oops! Something went wrong and we failed to stop video!' + - '(SRD Failure)'); - - } - ); -} - -$(document).bind('setLocalDescription.jingle', function (event, sid) { - // put our ssrcs into presence so other clients can identify our stream - var sess = connection.jingle.sessions[sid]; - var newssrcs = []; - var media = simulcast.parseMedia(sess.peerconnection.localDescription); - media.forEach(function (media) { - - if(Object.keys(media.sources).length > 0) { - // TODO(gp) maybe exclude FID streams? - Object.keys(media.sources).forEach(function (ssrc) { - newssrcs.push({ - 'ssrc': ssrc, - 'type': media.type, - 'direction': media.direction - }); - }); - } - else if(sess.localStreamsSSRC && sess.localStreamsSSRC[media.type]) - { - newssrcs.push({ - 'ssrc': sess.localStreamsSSRC[media.type], - 'type': media.type, - 'direction': media.direction - }); - } - - }); - - console.log('new ssrcs', newssrcs); - - // Have to clear presence map to get rid of removed streams - connection.emuc.clearPresenceMedia(); - - if (newssrcs.length > 0) { - for (var i = 1; i <= newssrcs.length; i ++) { - // Change video type to screen - if (newssrcs[i-1].type === 'video' && desktopsharing.isUsingScreenStream()) { - newssrcs[i-1].type = 'screen'; - } - connection.emuc.addMediaToPresence(i, - newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction); - } - - connection.emuc.sendPresence(); - } -}); - -$(document).bind('iceconnectionstatechange.jingle', function (event, sid, session) { - switch (session.peerconnection.iceConnectionState) { - case 'checking': - session.timeChecking = (new Date()).getTime(); - session.firstconnect = true; - break; - case 'completed': // on caller side - case 'connected': - if (session.firstconnect) { - session.firstconnect = false; - var metadata = {}; - metadata.setupTime = (new Date()).getTime() - session.timeChecking; - session.peerconnection.getStats(function (res) { - if(res && res.result) { - res.result().forEach(function (report) { - if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') { - metadata.localCandidateType = report.stat('googLocalCandidateType'); - metadata.remoteCandidateType = report.stat('googRemoteCandidateType'); - - // log pair as well so we can get nice pie charts - metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType'); - - if (report.stat('googRemoteAddress').indexOf('[') === 0) { - metadata.ipv6 = true; - } - } - }); - } - }); - } - break; - } -}); - -$(document).bind('presence.muc', function (event, jid, info, pres) { - - //check if the video bridge is available - if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) { - bridgeIsDown = true; - UI.messageHandler.showError("Error", - "Jitsi Videobridge is currently unavailable. Please try again later!"); - } - - if (info.isFocus) - { - return; - } - - // Remove old ssrcs coming from the jid - Object.keys(ssrc2jid).forEach(function (ssrc) { - if (ssrc2jid[ssrc] == jid) { - delete ssrc2jid[ssrc]; - delete ssrc2videoType[ssrc]; - } - }); - - $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) { - //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc')); - var ssrcV = ssrc.getAttribute('ssrc'); - ssrc2jid[ssrcV] = jid; - notReceivedSSRCs.push(ssrcV); - - var type = ssrc.getAttribute('type'); - ssrc2videoType[ssrcV] = type; - - // might need to update the direction if participant just went from sendrecv to recvonly - if (type === 'video' || type === 'screen') { - var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video'); - switch (ssrc.getAttribute('direction')) { - case 'sendrecv': - el.show(); - break; - case 'recvonly': - el.hide(); - // FIXME: Check if we have to change large video - //VideoLayout.updateLargeVideo(el); - break; - } - } - }); - - var displayName = !config.displayJids - ? info.displayName : Strophe.getResourceFromJid(jid); - - if (displayName && displayName.length > 0) - $(document).trigger('displaynamechanged', - [jid, displayName]); - /*if (focus !== null && info.displayName !== null) { - focus.setEndpointDisplayName(jid, info.displayName); - }*/ - - //check if the video bridge is available - if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) { - bridgeIsDown = true; - UI.messageHandler.showError("Error", - "Jitsi Videobridge is currently unavailable. Please try again later!"); - } - - var id = $(pres).find('>userID').text(); - var email = $(pres).find('>email'); - if(email.length > 0) { - id = email.text(); - } - UI.setUserAvatar(jid, id); - -}); - -$(document).bind('kicked.muc', function (event, jid) { - console.info(jid + " has been kicked from MUC!"); - if (connection.emuc.myroomjid === jid) { - sessionTerminated = true; - disposeConference(false); - connection.emuc.doLeave(); - UI.messageHandler.openMessageDialog("Session Terminated", - "Ouch! You have been kicked out of the meet!"); - } -}); - -$(document).bind('passwordrequired.main', function (event) { - console.log('password is required'); - - UI.messageHandler.openTwoButtonDialog(null, - '

Password required

' + - '' + - '', - true, - "Ok", - function (e, v, m, f) { - if (v) { - var username = document.getElementById('passwordrequired.username'); - var password = document.getElementById('passwordrequired.password'); - - if (username.value !== null && password.value != null) { - connect(username.value, password.value); - } - } - }, - function (event) { - document.getElementById('passwordrequired.username').focus(); - } - ); -}); - -/** - * Checks if video identified by given src is desktop stream. - * @param videoSrc eg. - * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395 - * @returns {boolean} - */ -function isVideoSrcDesktop(jid) { - // FIXME: fix this mapping mess... - // figure out if large video is desktop stream or just a camera - - if(!jid) - return false; - var isDesktop = false; - if (connection.emuc.myroomjid && - Strophe.getResourceFromJid(connection.emuc.myroomjid) === jid) { - // local video - isDesktop = desktopsharing.isUsingScreenStream(); - } else { - // Do we have associations... - var videoSsrc = jid2Ssrc[jid]; - if (videoSsrc) { - var videoType = ssrc2videoType[videoSsrc]; - if (videoType) { - // Finally there... - isDesktop = videoType === 'screen'; - } else { - console.error("No video type for ssrc: " + videoSsrc); - } - } else { - console.error("No ssrc for jid: " + jid); - } - } - return isDesktop; -} - -/** - * Mutes/unmutes the local video. - * - * @param mute true to mute the local video; otherwise, false - * @param options an object which specifies optional arguments such as the - * boolean key byUser with default value true which - * specifies whether the method was initiated in response to a user command (in - * contrast to an automatic decision taken by the application logic) - */ -function setVideoMute(mute, options) { - if (connection && RTC.localVideo) { - if (activecall) { - activecall.setVideoMute( - mute, - function (mute) { - var video = $('#video'); - var communicativeClass = "icon-camera"; - var muteClass = "icon-camera icon-camera-disabled"; - - if (mute) { - video.removeClass(communicativeClass); - video.addClass(muteClass); - } else { - video.removeClass(muteClass); - video.addClass(communicativeClass); - } - connection.emuc.addVideoInfoToPresence(mute); - connection.emuc.sendPresence(); - }, - options); - } - } -} - -$(document).on('inlastnchanged', function (event, oldValue, newValue) { - if (config.muteLocalVideoIfNotInLastN) { - setVideoMute(!newValue, { 'byUser': false }); - } -}); - -/** - * Mutes/unmutes the local video. - */ -function toggleVideo() { - buttonClick("#video", "icon-camera icon-camera-disabled"); - - if (connection && activecall && RTC.localVideo ) { - setVideoMute(!RTC.localVideo.isMuted()); - } -} - -/** - * Mutes / unmutes audio for the local participant. - */ -function toggleAudio() { - setAudioMuted(!RTC.localAudio.isMuted()); -} - -/** - * Sets muted audio state for the local participant. - */ -function setAudioMuted(mute) { - if (!(connection && RTC.localAudio)) { - preMuted = mute; - // We still click the button. - buttonClick("#mute", "icon-microphone icon-mic-disabled"); - return; - } - - if (forceMuted && !mute) { - console.info("Asking focus for unmute"); - connection.moderate.setMute(connection.emuc.myroomjid, mute); - // FIXME: wait for result before resetting muted status - forceMuted = false; - } - - if (mute == RTC.localAudio.isMuted()) { - // Nothing to do - return; - } - - // It is not clear what is the right way to handle multiple tracks. - // So at least make sure that they are all muted or all unmuted and - // that we send presence just once. - RTC.localAudio.mute(); - // isMuted is the opposite of audioEnabled - connection.emuc.addAudioInfoToPresence(mute); - connection.emuc.sendPresence(); - UI.showLocalAudioIndicator(mute); - - buttonClick("#mute", "icon-microphone icon-mic-disabled"); } @@ -706,60 +35,12 @@ $(document).ready(function () { UI.start(); statistics.start(); - Moderator.init(); - // Set default desktop sharing method desktopsharing.init(); }); $(window).bind('beforeunload', function () { - if (connection && connection.connected) { - // ensure signout - $.ajax({ - type: 'POST', - url: config.bosh, - async: false, - cache: false, - contentType: 'application/xml', - data: "", - success: function (data) { - console.log('signed out'); - console.log(data); - }, - error: function (XMLHttpRequest, textStatus, errorThrown) { - console.log('signout error', textStatus + ' (' + errorThrown + ')'); - } - }); - } - disposeConference(true); if(API.isEnabled()) API.dispose(); }); -function disposeConference(onUnload) { - UI.onDisposeConference(onUnload); - var handler = activecall; - if (handler && handler.peerconnection) { - // FIXME: probably removing streams is not required and close() should - // be enough - if (RTC.localAudio) { - handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload); - } - if (RTC.localVideo) { - handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload); - } - handler.peerconnection.close(); - } - statistics.onDisposeConference(onUnload); - activecall = null; -} - -/** - * Changes the style class of the element given by id. - */ -function buttonClick(id, classname) { - $(id).toggleClass(classname); // add the class to the clicked element -} diff --git a/estos_log.js b/estos_log.js deleted file mode 100644 index f822fa7a1..000000000 --- a/estos_log.js +++ /dev/null @@ -1,17 +0,0 @@ -/* global Strophe */ -Strophe.addConnectionPlugin('logger', { - // logs raw stanzas and makes them available for download as JSON - connection: null, - log: [], - init: function (conn) { - this.connection = conn; - this.connection.rawInput = this.log_incoming.bind(this); - this.connection.rawOutput = this.log_outgoing.bind(this); - }, - log_incoming: function (stanza) { - this.log.push([new Date().getTime(), 'incoming', stanza]); - }, - log_outgoing: function (stanza) { - this.log.push([new Date().getTime(), 'outgoing', stanza]); - }, -}); diff --git a/index.html b/index.html index a50ef1b2c..2c4520ac1 100644 --- a/index.html +++ b/index.html @@ -11,16 +11,10 @@ - - - - - - @@ -29,22 +23,20 @@ + - - + + - - + - - diff --git a/keyboard_shortcut.js b/keyboard_shortcut.js index 64be13994..b74d52cc3 100644 --- a/keyboard_shortcut.js +++ b/keyboard_shortcut.js @@ -14,20 +14,20 @@ var KeyboardShortcut = (function(my) { 77: { character: "M", id: "mutePopover", - function: toggleAudio + function: UI.toggleAudio }, 84: { character: "T", function: function() { if(!RTC.localAudio.isMuted()) { - toggleAudio(); + UI.toggleAudio(); } } }, 86: { character: "V", id: "toggleVideoPopover", - function: toggleVideo + function: UI.toggleVideo } }; @@ -53,7 +53,7 @@ var KeyboardShortcut = (function(my) { if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) { if(e.which === "T".charCodeAt(0)) { if(RTC.localAudio.isMuted()) { - toggleAudio(); + UI.toggleAudio(); } } } diff --git a/libs/modules/API.bundle.js b/libs/modules/API.bundle.js index ca7317bc4..3c10be005 100644 --- a/libs/modules/API.bundle.js +++ b/libs/modules/API.bundle.js @@ -19,8 +19,8 @@ var commands = { displayName: UI.inputDisplayNameHandler, - muteAudio: toggleAudio, - muteVideo: toggleVideo, + muteAudio: UI.toggleAudio, + muteVideo: UI.toggleVideo, toggleFilmStrip: UI.toggleFilmStrip, toggleChat: UI.toggleChat, toggleContactList: UI.toggleContactList @@ -204,4 +204,5 @@ var API = { module.exports = API; },{}]},{},[1])(1) -}); \ No newline at end of file +}); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi91c3IvbG9jYWwvbGliL25vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL0FQSS9BUEkuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiLyoqXG4gKiBJbXBsZW1lbnRzIEFQSSBjbGFzcyB0aGF0IGNvbW11bmljYXRlcyB3aXRoIGV4dGVybmFsIGFwaSBjbGFzc1xuICogYW5kIHByb3ZpZGVzIGludGVyZmFjZSB0byBhY2Nlc3MgSml0c2kgTWVldCBmZWF0dXJlcyBieSBleHRlcm5hbFxuICogYXBwbGljYXRpb25zIHRoYXQgZW1iZWQgSml0c2kgTWVldFxuICovXG5cblxuXG4vKipcbiAqIExpc3Qgb2YgdGhlIGF2YWlsYWJsZSBjb21tYW5kcy5cbiAqIEB0eXBlIHt7XG4gKiAgICAgICAgICAgICAgZGlzcGxheU5hbWU6IGlucHV0RGlzcGxheU5hbWVIYW5kbGVyLFxuICogICAgICAgICAgICAgIG11dGVBdWRpbzogdG9nZ2xlQXVkaW8sXG4gKiAgICAgICAgICAgICAgbXV0ZVZpZGVvOiB0b2dnbGVWaWRlbyxcbiAqICAgICAgICAgICAgICBmaWxtU3RyaXA6IHRvZ2dsZUZpbG1TdHJpcFxuICogICAgICAgICAgfX1cbiAqL1xudmFyIGNvbW1hbmRzID1cbntcbiAgICBkaXNwbGF5TmFtZTogVUkuaW5wdXREaXNwbGF5TmFtZUhhbmRsZXIsXG4gICAgbXV0ZUF1ZGlvOiBVSS50b2dnbGVBdWRpbyxcbiAgICBtdXRlVmlkZW86IFVJLnRvZ2dsZVZpZGVvLFxuICAgIHRvZ2dsZUZpbG1TdHJpcDogVUkudG9nZ2xlRmlsbVN0cmlwLFxuICAgIHRvZ2dsZUNoYXQ6IFVJLnRvZ2dsZUNoYXQsXG4gICAgdG9nZ2xlQ29udGFjdExpc3Q6IFVJLnRvZ2dsZUNvbnRhY3RMaXN0XG59O1xuXG5cbi8qKlxuICogTWFwcyB0aGUgc3VwcG9ydGVkIGV2ZW50cyBhbmQgdGhlaXIgc3RhdHVzXG4gKiAodHJ1ZSBpdCB0aGUgZXZlbnQgaXMgZW5hYmxlZCBhbmQgZmFsc2UgaWYgaXQgaXMgZGlzYWJsZWQpXG4gKiBAdHlwZSB7e1xuICogICAgICAgICAgICAgIGluY29taW5nTWVzc2FnZTogYm9vbGVhbixcbiAqICAgICAgICAgICAgICBvdXRnb2luZ01lc3NhZ2U6IGJvb2xlYW4sXG4gKiAgICAgICAgICAgICAgZGlzcGxheU5hbWVDaGFuZ2U6IGJvb2xlYW4sXG4gKiAgICAgICAgICAgICAgcGFydGljaXBhbnRKb2luZWQ6IGJvb2xlYW4sXG4gKiAgICAgICAgICAgICAgcGFydGljaXBhbnRMZWZ0OiBib29sZWFuXG4gKiAgICAgIH19XG4gKi9cbnZhciBldmVudHMgPVxue1xuICAgIGluY29taW5nTWVzc2FnZTogZmFsc2UsXG4gICAgb3V0Z29pbmdNZXNzYWdlOmZhbHNlLFxuICAgIGRpc3BsYXlOYW1lQ2hhbmdlOiBmYWxzZSxcbiAgICBwYXJ0aWNpcGFudEpvaW5lZDogZmFsc2UsXG4gICAgcGFydGljaXBhbnRMZWZ0OiBmYWxzZVxufTtcblxuLyoqXG4gKiBQcm9jZXNzZXMgY29tbWFuZHMgZnJvbSBleHRlcm5hbCBhcHBsaWNhaXRvbi5cbiAqIEBwYXJhbSBtZXNzYWdlIHRoZSBvYmplY3Qgd2l0aCB0aGUgY29tbWFuZFxuICovXG5mdW5jdGlvbiBwcm9jZXNzQ29tbWFuZChtZXNzYWdlKVxue1xuICAgIGlmKG1lc3NhZ2UuYWN0aW9uICE9IFwiZXhlY3V0ZVwiKVxuICAgIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihcIlVua25vd24gYWN0aW9uIG9mIHRoZSBtZXNzYWdlXCIpO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGZvcih2YXIga2V5IGluIG1lc3NhZ2UpXG4gICAge1xuICAgICAgICBpZihjb21tYW5kc1trZXldKVxuICAgICAgICAgICAgY29tbWFuZHNba2V5XS5hcHBseShudWxsLCBtZXNzYWdlW2tleV0pO1xuICAgIH1cbn1cblxuLyoqXG4gKiBQcm9jZXNzZXMgZXZlbnRzIG9iamVjdHMgZnJvbSBleHRlcm5hbCBhcHBsaWNhdGlvbnNcbiAqIEBwYXJhbSBldmVudCB0aGUgZXZlbnRcbiAqL1xuZnVuY3Rpb24gcHJvY2Vzc0V2ZW50KGV2ZW50KSB7XG4gICAgaWYoIWV2ZW50LmFjdGlvbilcbiAgICB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoXCJFdmVudCB3aXRoIG5vIGFjdGlvbiBpcyByZWNlaXZlZC5cIik7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB2YXIgaSA9IDA7XG4gICAgc3dpdGNoKGV2ZW50LmFjdGlvbilcbiAgICB7XG4gICAgICAgIGNhc2UgXCJhZGRcIjpcbiAgICAgICAgICAgIGZvcig7IGkgPCBldmVudC5ldmVudHMubGVuZ3RoOyBpKyspXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgZXZlbnRzW2V2ZW50LmV2ZW50c1tpXV0gPSB0cnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgXCJyZW1vdmVcIjpcbiAgICAgICAgICAgIGZvcig7IGkgPCBldmVudC5ldmVudHMubGVuZ3RoOyBpKyspXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgZXZlbnRzW2V2ZW50LmV2ZW50c1tpXV0gPSBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgY29uc29sZS5lcnJvcihcIlVua25vd24gYWN0aW9uIGZvciBldmVudC5cIik7XG4gICAgfVxuXG59XG5cbi8qKlxuICogU2VuZHMgbWVzc2FnZSB0byB0aGUgZXh0ZXJuYWwgYXBwbGljYXRpb24uXG4gKiBAcGFyYW0gb2JqZWN0XG4gKi9cbmZ1bmN0aW9uIHNlbmRNZXNzYWdlKG9iamVjdCkge1xuICAgIHdpbmRvdy5wYXJlbnQucG9zdE1lc3NhZ2UoSlNPTi5zdHJpbmdpZnkob2JqZWN0KSwgXCIqXCIpO1xufVxuXG4vKipcbiAqIFByb2Nlc3NlcyBhIG1lc3NhZ2UgZXZlbnQgZnJvbSB0aGUgZXh0ZXJuYWwgYXBwbGljYXRpb25cbiAqIEBwYXJhbSBldmVudCB0aGUgbWVzc2FnZSBldmVudFxuICovXG5mdW5jdGlvbiBwcm9jZXNzTWVzc2FnZShldmVudClcbntcbiAgICB2YXIgbWVzc2FnZTtcbiAgICB0cnkge1xuICAgICAgICBtZXNzYWdlID0gSlNPTi5wYXJzZShldmVudC5kYXRhKTtcbiAgICB9IGNhdGNoIChlKSB7fVxuXG4gICAgaWYoIW1lc3NhZ2UudHlwZSlcbiAgICAgICAgcmV0dXJuO1xuICAgIHN3aXRjaCAobWVzc2FnZS50eXBlKVxuICAgIHtcbiAgICAgICAgY2FzZSBcImNvbW1hbmRcIjpcbiAgICAgICAgICAgIHByb2Nlc3NDb21tYW5kKG1lc3NhZ2UpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgXCJldmVudFwiOlxuICAgICAgICAgICAgcHJvY2Vzc0V2ZW50KG1lc3NhZ2UpO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKFwiVW5rbm93biB0eXBlIG9mIHRoZSBtZXNzYWdlXCIpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgIH1cblxufVxuXG52YXIgQVBJID0ge1xuICAgIC8qKlxuICAgICAqIENoZWNrIHdoZXRoZXIgdGhlIEFQSSBzaG91bGQgYmUgZW5hYmxlZCBvciBub3QuXG4gICAgICogQHJldHVybnMge2Jvb2xlYW59XG4gICAgICovXG4gICAgaXNFbmFibGVkOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBoYXNoID0gbG9jYXRpb24uaGFzaDtcbiAgICAgICAgaWYoaGFzaCAmJiBoYXNoLmluZGV4T2YoXCJleHRlcm5hbFwiKSA+IC0xICYmIHdpbmRvdy5wb3N0TWVzc2FnZSlcbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfSxcbiAgICAvKipcbiAgICAgKiBJbml0aWFsaXplcyB0aGUgQVBJQ29ubmVjdG9yLiBTZXR1cHMgbWVzc2FnZSBldmVudCBsaXN0ZW5lcnMgdGhhdCB3aWxsXG4gICAgICogcmVjZWl2ZSBpbmZvcm1hdGlvbiBmcm9tIGV4dGVybmFsIGFwcGxpY2F0aW9ucyB0aGF0IGVtYmVkIEppdHNpIE1lZXQuXG4gICAgICogSXQgYWxzbyBzZW5kcyBhIG1lc3NhZ2UgdG8gdGhlIGV4dGVybmFsIGFwcGxpY2F0aW9uIHRoYXQgQVBJQ29ubmVjdG9yXG4gICAgICogaXMgaW5pdGlhbGl6ZWQuXG4gICAgICovXG4gICAgaW5pdDogZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAod2luZG93LmFkZEV2ZW50TGlzdGVuZXIpXG4gICAgICAgIHtcbiAgICAgICAgICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJyxcbiAgICAgICAgICAgICAgICBwcm9jZXNzTWVzc2FnZSwgZmFsc2UpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2VcbiAgICAgICAge1xuICAgICAgICAgICAgd2luZG93LmF0dGFjaEV2ZW50KCdvbm1lc3NhZ2UnLCBwcm9jZXNzTWVzc2FnZSk7XG4gICAgICAgIH1cbiAgICAgICAgc2VuZE1lc3NhZ2Uoe3R5cGU6IFwic3lzdGVtXCIsIGxvYWRlZDogdHJ1ZX0pO1xuICAgIH0sXG4gICAgLyoqXG4gICAgICogQ2hlY2tzIHdoZXRoZXIgdGhlIGV2ZW50IGlzIGVuYWJsZWQgb3Qgbm90LlxuICAgICAqIEBwYXJhbSBuYW1lIHRoZSBuYW1lIG9mIHRoZSBldmVudC5cbiAgICAgKiBAcmV0dXJucyB7Kn1cbiAgICAgKi9cbiAgICBpc0V2ZW50RW5hYmxlZDogZnVuY3Rpb24gKG5hbWUpIHtcbiAgICAgICAgcmV0dXJuIGV2ZW50c1tuYW1lXTtcbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogU2VuZHMgZXZlbnQgb2JqZWN0IHRvIHRoZSBleHRlcm5hbCBhcHBsaWNhdGlvbiB0aGF0IGhhcyBiZWVuIHN1YnNjcmliZWRcbiAgICAgKiBmb3IgdGhhdCBldmVudC5cbiAgICAgKiBAcGFyYW0gbmFtZSB0aGUgbmFtZSBldmVudFxuICAgICAqIEBwYXJhbSBvYmplY3QgZGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIGV2ZW50XG4gICAgICovXG4gICAgdHJpZ2dlckV2ZW50OiBmdW5jdGlvbiAobmFtZSwgb2JqZWN0KSB7XG4gICAgICAgIGlmKHRoaXMuaXNFbmFibGVkKCkgJiYgdGhpcy5pc0V2ZW50RW5hYmxlZChuYW1lKSlcbiAgICAgICAgICAgIHNlbmRNZXNzYWdlKHtcbiAgICAgICAgICAgICAgICB0eXBlOiBcImV2ZW50XCIsIGFjdGlvbjogXCJyZXN1bHRcIiwgZXZlbnQ6IG5hbWUsIHJlc3VsdDogb2JqZWN0fSk7XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFJlbW92ZXMgdGhlIGxpc3RlbmVycy5cbiAgICAgKi9cbiAgICBkaXNwb3NlOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGlmKHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKVxuICAgICAgICB7XG4gICAgICAgICAgICB3aW5kb3cucmVtb3ZlRXZlbnRMaXN0ZW5lcihcIm1lc3NhZ2VcIixcbiAgICAgICAgICAgICAgICBwcm9jZXNzTWVzc2FnZSwgZmFsc2UpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2VcbiAgICAgICAge1xuICAgICAgICAgICAgd2luZG93LmRldGFjaEV2ZW50KCdvbm1lc3NhZ2UnLCBwcm9jZXNzTWVzc2FnZSk7XG4gICAgICAgIH1cblxuICAgIH1cblxuXG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEFQSTsiXX0= diff --git a/libs/modules/RTC.bundle.js b/libs/modules/RTC.bundle.js index cf4ab3e03..bf8405b4f 100644 --- a/libs/modules/RTC.bundle.js +++ b/libs/modules/RTC.bundle.js @@ -1,5 +1,5 @@ !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.RTC=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;ovideo'); + switch (stream.direction) { + case 'sendrecv': + el.show(); + break; + case 'recvonly': + el.hide(); + // FIXME: Check if we have to change large video + //VideoLayout.updateLargeVideo(el); + break; + } + } + } - + }); + xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged); + xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined); } function bindEvents() @@ -118,10 +153,6 @@ function bindEvents() function () { VideoLayout.resizeLargeVideoContainer(); VideoLayout.positionLarge(); - isFullScreen = document.fullScreen || - document.mozFullScreen || - document.webkitIsFullScreen; - } ); @@ -256,11 +287,6 @@ UI.start = function () { }; - -UI.setUserAvatar = function (jid, id) { - Avatar.setUserAvatar(jid, id); -}; - UI.toggleSmileys = function () { Chat.toggleSmileys(); }; @@ -279,7 +305,7 @@ UI.updateChatConversation = function (from, displayName, message) { return Chat.updateChatConversation(from, displayName, message); }; -UI.onMucJoined = function (jid, info) { +function onMucJoined(jid, info) { Toolbar.updateRoomUrl(window.location.href); document.getElementById('localNick').appendChild( document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)') @@ -294,15 +320,14 @@ UI.onMucJoined = function (jid, info) { // Show authenticate button if needed Toolbar.showAuthenticateButton( - Moderator.isExternalAuthEnabled() && !Moderator.isModerator()); + xmpp.isExternalAuthEnabled() && !xmpp.isModerator()); var displayName = !config.displayJids ? info.displayName : Strophe.getResourceFromJid(jid); if (displayName) - $(document).trigger('displaynamechanged', - ['localVideoContainer', displayName + ' (me)']); -}; + onDisplayNameChanged('localVideoContainer', displayName + ' (me)'); +} UI.initEtherpad = function (name) { Etherpad.init(name); @@ -358,23 +383,19 @@ UI.toggleContactList = function () { UI.onLocalRoleChange = function (jid, info, pres) { console.info("My role changed, new role: " + info.role); - var isModerator = Moderator.isModerator(); + var isModerator = xmpp.isModerator(); VideoLayout.showModeratorIndicator(); Toolbar.showAuthenticateButton( - Moderator.isExternalAuthEnabled() && !isModerator); + xmpp.isExternalAuthEnabled() && !isModerator); if (isModerator) { - Toolbar.closeAuthenticationWindow(); + Authentication.closeAuthenticationWindow(); messageHandler.notify( 'Me', 'connected', 'Moderator rights granted !'); } }; -UI.onDisposeConference = function (unload) { - Toolbar.showAuthenticateButton(false); -}; - UI.onModeratorStatusChanged = function (isModerator) { Toolbar.showSipCallButton(isModerator); @@ -415,40 +436,11 @@ UI.onPasswordReqiured = function (callback) { ); }; -UI.onAuthenticationRequired = function () { - // This is the loop that will wait for the room to be created by - // someone else. 'auth_required.moderator' will bring us back here. - authRetryId = window.setTimeout( - function () { - Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus); - }, 5000); - // Show prompt only if it's not open - if (authDialog !== null) { - return; - } - // extract room name from 'room@muc.server.net' - var room = roomName.substr(0, roomName.indexOf('@')); - - authDialog = messageHandler.openDialog( - 'Stop', - 'Authentication is required to create room:
' + room + - '
You can either authenticate to create the room or ' + - 'just wait for someone else to do so.', - true, - { - Authenticate: 'authNow' - }, - function (onSubmitEvent, submitValue) { - - // Do not close the dialog yet - onSubmitEvent.preventDefault(); - - // Open login popup - if (submitValue === 'authNow') { - Toolbar.authenticateClicked(); - } - } - ); +UI.onAuthenticationRequired = function (intervalCallback) { + Authentication.openAuthenticationDialog( + roomName, intervalCallback, function () { + Toolbar.authenticateClicked(); + }); }; UI.setRecordingButtonState = function (state) { @@ -512,6 +504,8 @@ UI.showLocalAudioIndicator = function (mute) { }; UI.generateRoomName = function() { + if(roomName) + return roomName; var roomnode = null; var path = window.location.pathname; @@ -541,6 +535,7 @@ UI.generateRoomName = function() { } roomName = roomnode + '@' + config.hosts.muc; + return roomName; }; @@ -557,30 +552,151 @@ UI.dockToolbar = function (isDock) { return ToolbarToggler.dockToolbar(isDock); }; +UI.getCreadentials = function () { + return { + bosh: document.getElementById('boshURL').value, + password: document.getElementById('password').value, + jid: document.getElementById('jid').value + }; +}; + +UI.disableConnect = function () { + document.getElementById('connect').disabled = true; +}; + +UI.showLoginPopup = function(callback) +{ + console.log('password is required'); + + UI.messageHandler.openTwoButtonDialog(null, + '

Password required

' + + '' + + '', + true, + "Ok", + function (e, v, m, f) { + if (v) { + var username = document.getElementById('passwordrequired.username'); + var password = document.getElementById('passwordrequired.password'); + + if (username.value !== null && password.value != null) { + callback(username.value, password.value); + } + } + }, + function (event) { + document.getElementById('passwordrequired.username').focus(); + } + ); +} + +UI.checkForNicknameAndJoin = function () { + + Authentication.closeAuthenticationDialog(); + Authentication.stopInterval(); + + var nick = null; + if (config.useNicks) { + nick = window.prompt('Your nickname (optional)'); + } + xmpp.joinRooom(roomName, config.useNicks, nick); +} + + function dump(elem, filename) { elem = elem.parentNode; elem.download = filename || 'meetlog.json'; elem.href = 'data:application/json;charset=utf-8,\n'; - var data = {}; - if (connection.jingle) { - data = connection.jingle.populateData(); - } + var data = xmpp.populateData(); var metadata = {}; metadata.time = new Date(); metadata.url = window.location.href; metadata.ua = navigator.userAgent; - if (connection.logger) { - metadata.xmpp = connection.logger.log; + var log = xmpp.getLogger(); + if (log) { + metadata.xmpp = log; } data.metadata = metadata; elem.href += encodeURIComponent(JSON.stringify(data, null, ' ')); return false; } +UI.getRoomName = function () { + return roomName; +} + +/** + * Mutes/unmutes the local video. + * + * @param mute true to mute the local video; otherwise, false + * @param options an object which specifies optional arguments such as the + * boolean key byUser with default value true which + * specifies whether the method was initiated in response to a user command (in + * contrast to an automatic decision taken by the application logic) + */ +function setVideoMute(mute, options) { + xmpp.setVideoMute( + mute, + function (mute) { + var video = $('#video'); + var communicativeClass = "icon-camera"; + var muteClass = "icon-camera icon-camera-disabled"; + + if (mute) { + video.removeClass(communicativeClass); + video.addClass(muteClass); + } else { + video.removeClass(muteClass); + video.addClass(communicativeClass); + } + }, + options); +} + +/** + * Mutes/unmutes the local video. + */ +UI.toggleVideo = function () { + UIUtil.buttonClick("#video", "icon-camera icon-camera-disabled"); + + setVideoMute(!RTC.localVideo.isMuted()); +}; + +/** + * Mutes / unmutes audio for the local participant. + */ +UI.toggleAudio = function() { + UI.setAudioMuted(!RTC.localAudio.isMuted()); +}; + +/** + * Sets muted audio state for the local participant. + */ +UI.setAudioMuted = function (mute) { + + if(!xmpp.setAudioMute(mute, function () { + UI.showLocalAudioIndicator(mute); + + UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled"); + })) + { + // We still click the button. + UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled"); + return; + } + +} + +UI.onLastNChanged = function (oldValue, newValue) { + if (config.muteLocalVideoIfNotInLastN) { + setVideoMute(!newValue, { 'byUser': false }); + } +} + module.exports = UI; -},{"./audio_levels/AudioLevels.js":2,"./avatar/Avatar":4,"./etherpad/Etherpad.js":5,"./prezi/Prezi.js":6,"./side_pannels/SidePanelToggler":7,"./side_pannels/chat/Chat.js":8,"./side_pannels/contactlist/ContactList":12,"./side_pannels/settings/Settings":13,"./side_pannels/settings/SettingsMenu":14,"./toolbars/BottomToolbar":15,"./toolbars/toolbar":17,"./toolbars/toolbartoggler":18,"./util/MessageHandler":20,"./videolayout/VideoLayout.js":23,"./welcome_page/RoomnameGenerator":24,"./welcome_page/WelcomePage":25}],2:[function(require,module,exports){ +},{"./audio_levels/AudioLevels.js":2,"./authentication/Authentication":4,"./avatar/Avatar":5,"./etherpad/Etherpad.js":6,"./prezi/Prezi.js":7,"./side_pannels/SidePanelToggler":8,"./side_pannels/chat/Chat.js":9,"./side_pannels/contactlist/ContactList":13,"./side_pannels/settings/Settings":14,"./side_pannels/settings/SettingsMenu":15,"./toolbars/BottomToolbar":16,"./toolbars/toolbar":18,"./toolbars/toolbartoggler":19,"./util/MessageHandler":21,"./util/UIUtil":22,"./videolayout/VideoLayout.js":24,"./welcome_page/RoomnameGenerator":25,"./welcome_page/WelcomePage":26}],2:[function(require,module,exports){ var CanvasUtil = require("./CanvasUtils"); /** @@ -670,10 +786,10 @@ var AudioLevels = (function(my) { drawContext.drawImage(canvasCache, 0, 0); if(resourceJid === AudioLevels.LOCAL_LEVEL) { - if(!connection.emuc.myroomjid) { + if(!xmpp.myJid()) { return; } - resourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid); + resourceJid = xmpp.myResource(); } if(resourceJid === largeVideoResourceJid) { @@ -804,8 +920,8 @@ var AudioLevels = (function(my) { function getVideoSpanId(resourceJid) { var videoSpanId = null; if (resourceJid === AudioLevels.LOCAL_LEVEL - || (connection.emuc.myroomjid && resourceJid - === Strophe.getResourceFromJid(connection.emuc.myroomjid))) + || (xmpp.myResource() && resourceJid + === xmpp.myResource())) videoSpanId = 'localVideoContainer'; else videoSpanId = 'participant_' + resourceJid; @@ -958,6 +1074,91 @@ var CanvasUtil = (function(my) { module.exports = CanvasUtil; },{}],4:[function(require,module,exports){ +/* Initial "authentication required" dialog */ +var authDialog = null; +/* Loop retry ID that wits for other user to create the room */ +var authRetryId = null; +var authenticationWindow = null; + +var Authentication = { + openAuthenticationDialog: function (roomName, intervalCallback, callback) { + // This is the loop that will wait for the room to be created by + // someone else. 'auth_required.moderator' will bring us back here. + authRetryId = window.setTimeout(intervalCallback , 5000); + // Show prompt only if it's not open + if (authDialog !== null) { + return; + } + // extract room name from 'room@muc.server.net' + var room = roomName.substr(0, roomName.indexOf('@')); + + authDialog = messageHandler.openDialog( + 'Stop', + 'Authentication is required to create room:
' + room + + '
You can either authenticate to create the room or ' + + 'just wait for someone else to do so.', + true, + { + Authenticate: 'authNow' + }, + function (onSubmitEvent, submitValue) { + + // Do not close the dialog yet + onSubmitEvent.preventDefault(); + + // Open login popup + if (submitValue === 'authNow') { + callback(); + } + } + ); + }, + closeAuthenticationWindow:function () { + if (authenticationWindow) { + authenticationWindow.close(); + authenticationWindow = null; + } + }, + focusAuthenticationWindow: function () { + // If auth window exists just bring it to the front + if (authenticationWindow) { + authenticationWindow.focus(); + return; + } + }, + closeAuthenticationDialog: function () { + // Close authentication dialog if opened + if (authDialog) { + UI.messageHandler.closeDialog(); + authDialog = null; + } + }, + createAuthenticationWindow: function (callback, url) { + authenticationWindow = messageHandler.openCenteredPopup( + url, 910, 660, + // On closed + function () { + // Close authentication dialog if opened + if (authDialog) { + messageHandler.closeDialog(); + authDialog = null; + } + callback(); + authenticationWindow = null; + }); + return authenticationWindow; + }, + stopInterval: function () { + // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice + if (authRetryId) { + window.clearTimeout(authRetryId); + authRetryId = null; + } + } +}; + +module.exports = Authentication; +},{}],5:[function(require,module,exports){ var Settings = require("../side_pannels/settings/Settings"); var users = {}; @@ -972,7 +1173,7 @@ function setVisibility(selector, show) { function isUserMuted(jid) { // XXX(gp) we may want to rename this method to something like // isUserStreaming, for example. - if (jid && jid != connection.emuc.myroomjid) { + if (jid && jid != xmpp.myJid()) { var resource = Strophe.getResourceFromJid(jid); if (!require("../videolayout/VideoLayout").isInLastN(resource)) { return true; @@ -986,7 +1187,7 @@ function isUserMuted(jid) { } function getGravatarUrl(id, size) { - if(id === connection.emuc.myroomjid || !id) { + if(id === xmpp.myJid() || !id) { id = Settings.getSettings().uid; } return 'https://www.gravatar.com/avatar/' + @@ -1017,7 +1218,7 @@ var Avatar = { // set the avatar in the settings menu if it is local user and get the // local video container - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { $('#avatar').get(0).src = thumbUrl; thumbnail = $('#localVideoContainer'); } @@ -1060,7 +1261,7 @@ var Avatar = { var video = $('#participant_' + resourceJid + '>video'); var avatar = $('#avatar_' + resourceJid); - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { video = $('#localVideoWrapper>video'); } if (show === undefined || show === null) { @@ -1090,7 +1291,7 @@ var Avatar = { */ updateActiveSpeakerAvatarSrc: function (jid) { if (!jid) { - jid = connection.emuc.findJidFromResource( + jid = xmpp.findJidFromResource( require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid); } var avatar = $("#activeSpeakerAvatar")[0]; @@ -1112,8 +1313,8 @@ var Avatar = { module.exports = Avatar; -},{"../side_pannels/settings/Settings":13,"../videolayout/VideoLayout":23}],5:[function(require,module,exports){ -/* global $, config, connection, dockToolbar, Moderator, +},{"../side_pannels/settings/Settings":14,"../videolayout/VideoLayout":24}],6:[function(require,module,exports){ +/* global $, config, dockToolbar, setLargeVideoVisible, Util */ var VideoLayout = require("../videolayout/VideoLayout"); @@ -1145,8 +1346,7 @@ function resize() { * Shares the Etherpad name with other participants. */ function shareEtherpad() { - connection.emuc.addEtherpadToPresence(etherpadName); - connection.emuc.sendPresence(); + xmpp.addToPresence("etherpad", etherpadName); } /** @@ -1309,7 +1509,7 @@ var Etherpad = { module.exports = Etherpad; -},{"../prezi/Prezi":6,"../util/UIUtil":21,"../videolayout/VideoLayout":23}],6:[function(require,module,exports){ +},{"../prezi/Prezi":7,"../util/UIUtil":22,"../videolayout/VideoLayout":24}],7:[function(require,module,exports){ var ToolbarToggler = require("../toolbars/ToolbarToggler"); var UIUtil = require("../util/UIUtil"); var VideoLayout = require("../videolayout/VideoLayout"); @@ -1342,7 +1542,7 @@ var Prezi = { * to load. */ openPreziDialog: function() { - var myprezi = connection.emuc.getPrezi(connection.emuc.myroomjid); + var myprezi = xmpp.getPrezi(); if (myprezi) { messageHandler.openTwoButtonDialog("Remove Prezi", "Are you sure you would like to remove your Prezi?", @@ -1350,8 +1550,7 @@ var Prezi = { "Remove", function(e,v,m,f) { if(v) { - connection.emuc.removePreziFromPresence(); - connection.emuc.sendPresence(); + xmpp.removePreziFromPresence(); } } ); @@ -1403,9 +1602,7 @@ var Prezi = { return false; } else { - connection.emuc - .addPreziToPresence(urlValue, 0); - connection.emuc.sendPresence(); + xmpp.addToPresence("prezi", urlValue); $.prompt.close(); } } @@ -1463,7 +1660,7 @@ function presentationAdded(event, jid, presUrl, currentSlide) { VideoLayout.resizeThumbnails(); var controlsEnabled = false; - if (jid === connection.emuc.myroomjid) + if (jid === xmpp.myJid()) controlsEnabled = true; setPresentationVisible(true); @@ -1503,15 +1700,14 @@ function presentationAdded(event, jid, presUrl, currentSlide) { preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) { console.log("prezi status", event.value); if (event.value == PreziPlayer.STATUS_CONTENT_READY) { - if (jid != connection.emuc.myroomjid) + if (jid != xmpp.myJid()) preziPlayer.flyToStep(currentSlide); } }); preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) { console.log("event value", event.value); - connection.emuc.addCurrentSlideToPresence(event.value); - connection.emuc.sendPresence(); + xmpp.addToPresence("preziSlide", event.value); }); $("#" + elementId).css( 'background-image', @@ -1665,13 +1861,14 @@ $(window).resize(function () { module.exports = Prezi; -},{"../toolbars/ToolbarToggler":16,"../util/MessageHandler":20,"../util/UIUtil":21,"../videolayout/VideoLayout":23}],7:[function(require,module,exports){ +},{"../toolbars/ToolbarToggler":17,"../util/MessageHandler":21,"../util/UIUtil":22,"../videolayout/VideoLayout":24}],8:[function(require,module,exports){ var Chat = require("./chat/Chat"); var ContactList = require("./contactlist/ContactList"); var Settings = require("./settings/Settings"); var SettingsMenu = require("./settings/SettingsMenu"); var VideoLayout = require("../videolayout/VideoLayout"); var ToolbarToggler = require("../toolbars/ToolbarToggler"); +var UIUtil = require("../util/UIUtil"); /** * Toggler for the chat, contact list, settings menu, etc.. @@ -1778,7 +1975,7 @@ var PanelToggler = (function(my) { * @param onClose function to be called if the window is going to be closed */ var toggle = function(object, selector, onOpenComplete, onOpen, onClose) { - buttonClick(buttons[selector], "active"); + UIUtil.buttonClick(buttons[selector], "active"); if (object.isVisible()) { $("#toast-container").animate({ @@ -1808,7 +2005,7 @@ var PanelToggler = (function(my) { if(currentlyOpen) { var current = $(currentlyOpen); - buttonClick(buttons[currentlyOpen], "active"); + UIUtil.buttonClick(buttons[currentlyOpen], "active"); current.css('z-index', 4); setTimeout(function () { current.css('display', 'none'); @@ -1921,8 +2118,8 @@ var PanelToggler = (function(my) { }(PanelToggler || {})); module.exports = PanelToggler; -},{"../toolbars/ToolbarToggler":16,"../videolayout/VideoLayout":23,"./chat/Chat":8,"./contactlist/ContactList":12,"./settings/Settings":13,"./settings/SettingsMenu":14}],8:[function(require,module,exports){ -/* global $, Util, connection, nickname:true, showToolbar */ +},{"../toolbars/ToolbarToggler":17,"../util/UIUtil":22,"../videolayout/VideoLayout":24,"./chat/Chat":9,"./contactlist/ContactList":13,"./settings/Settings":14,"./settings/SettingsMenu":15}],9:[function(require,module,exports){ +/* global $, Util, nickname:true, showToolbar */ var Replacement = require("./Replacement"); var CommandsProcessor = require("./Commands"); var ToolbarToggler = require("../../toolbars/ToolbarToggler"); @@ -2108,8 +2305,7 @@ var Chat = (function (my) { nickname = val; window.localStorage.displayname = nickname; - connection.emuc.addDisplayNameToPresence(nickname); - connection.emuc.sendPresence(); + xmpp.addToPresence("displayName", nickname); Chat.setChatConversationMode(true); @@ -2132,7 +2328,7 @@ var Chat = (function (my) { else { var message = Util.escapeHtml(value); - connection.emuc.sendMessage(message, nickname); + xmpp.sendChatMessage(message, nickname); } } }); @@ -2158,7 +2354,7 @@ var Chat = (function (my) { my.updateChatConversation = function (from, displayName, message) { var divClassName = ''; - if (connection.emuc.myroomjid === from) { + if (xmpp.myJid() === from) { divClassName = "localuser"; } else { @@ -2282,7 +2478,7 @@ var Chat = (function (my) { return my; }(Chat || {})); module.exports = Chat; -},{"../../toolbars/ToolbarToggler":16,"../SidePanelToggler":7,"./Commands":9,"./Replacement":10,"./smileys.json":11}],9:[function(require,module,exports){ +},{"../../toolbars/ToolbarToggler":17,"../SidePanelToggler":8,"./Commands":10,"./Replacement":11,"./smileys.json":12}],10:[function(require,module,exports){ /** * List with supported commands. The keys are the names of the commands and * the value is the function that processes the message. @@ -2317,7 +2513,7 @@ function getCommand(message) function processTopic(commandArguments) { var topic = Util.escapeHtml(commandArguments); - connection.emuc.setSubject(topic); + xmpp.setSubject(topic); } /** @@ -2378,7 +2574,7 @@ CommandsProcessor.prototype.processCommand = function() }; module.exports = CommandsProcessor; -},{}],10:[function(require,module,exports){ +},{}],11:[function(require,module,exports){ var Smileys = require("./smileys.json"); /** * Processes links and smileys in "body" @@ -2442,7 +2638,7 @@ module.exports = { linkify: linkify }; -},{"./smileys.json":11}],11:[function(require,module,exports){ +},{"./smileys.json":12}],12:[function(require,module,exports){ module.exports={ "smileys": { "smiley1": ":)", @@ -2492,7 +2688,7 @@ module.exports={ } } -},{}],12:[function(require,module,exports){ +},{}],13:[function(require,module,exports){ var numberOfContacts = 0; var notificationInterval; @@ -2541,23 +2737,6 @@ function createDisplayNameParagraph(displayName) { } -/** - * Indicates that the display name has changed. - */ -$(document).bind( 'displaynamechanged', - function (event, peerJid, displayName) { - if (peerJid === 'localVideoContainer') - peerJid = connection.emuc.myroomjid; - - var resourceJid = Strophe.getResourceFromJid(peerJid); - - var contactName = $('#contactlist #' + resourceJid + '>p'); - - if (contactName && displayName && displayName.length > 0) - contactName.html(displayName); - }); - - function stopGlowing(glower) { window.clearInterval(notificationInterval); notificationInterval = false; @@ -2622,7 +2801,7 @@ var ContactList = { var clElement = contactlist.get(0); - if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid) + if (resourceJid === xmpp.myResource() && $('#contactlist>ul .title')[0].nextSibling.nextSibling) { clElement.insertBefore(newContact, $('#contactlist>ul .title')[0].nextSibling.nextSibling); @@ -2677,11 +2856,23 @@ var ContactList = { } else { contact.removeClass('clickable'); } + }, + + onDisplayNameChange: function (peerJid, displayName) { + if (peerJid === 'localVideoContainer') + peerJid = xmpp.myJid(); + + var resourceJid = Strophe.getResourceFromJid(peerJid); + + var contactName = $('#contactlist #' + resourceJid + '>p'); + + if (contactName && displayName && displayName.length > 0) + contactName.html(displayName); } }; module.exports = ContactList; -},{}],13:[function(require,module,exports){ +},{}],14:[function(require,module,exports){ var email = ''; var displayName = ''; var userId; @@ -2741,7 +2932,7 @@ var Settings = module.exports = Settings; -},{}],14:[function(require,module,exports){ +},{}],15:[function(require,module,exports){ var Avatar = require("../../avatar/Avatar"); var Settings = require("./Settings"); @@ -2754,16 +2945,15 @@ var SettingsMenu = { if(newDisplayName) { var displayName = Settings.setDisplayName(newDisplayName); - connection.emuc.addDisplayNameToPresence(displayName); + xmpp.addToPresence("displayName", displayName, true); } - connection.emuc.addEmailToPresence(newEmail); + xmpp.addToPresence("email", newEmail); var email = Settings.setEmail(newEmail); - connection.emuc.sendPresence(); - Avatar.setUserAvatar(connection.emuc.myroomjid, email); + Avatar.setUserAvatar(xmpp.myJid(), email); }, isVisible: function() { @@ -2773,18 +2963,19 @@ var SettingsMenu = { setDisplayName: function(newDisplayName) { var displayName = Settings.setDisplayName(newDisplayName); $('#setDisplayName').get(0).value = displayName; + }, + + onDisplayNameChange: function(peerJid, newDisplayName) { + if(peerJid === 'localVideoContainer' || + peerJid === xmpp.myJid()) { + this.setDisplayName(newDisplayName); + } } }; -$(document).bind('displaynamechanged', function(event, peerJid, newDisplayName) { - if(peerJid === 'localVideoContainer' || - peerJid === connection.emuc.myroomjid) { - SettingsMenu.setDisplayName(newDisplayName); - } -}); module.exports = SettingsMenu; -},{"../../avatar/Avatar":4,"./Settings":13}],15:[function(require,module,exports){ +},{"../../avatar/Avatar":5,"./Settings":14}],16:[function(require,module,exports){ var PanelToggler = require("../side_pannels/SidePanelToggler"); var buttonHandlers = { @@ -2829,7 +3020,7 @@ var BottomToolbar = (function (my) { module.exports = BottomToolbar; -},{"../side_pannels/SidePanelToggler":7}],16:[function(require,module,exports){ +},{"../side_pannels/SidePanelToggler":8}],17:[function(require,module,exports){ /* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */ var toolbarTimeoutObject, @@ -2899,7 +3090,7 @@ var ToolbarToggler = { toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT; } - if (Moderator.isModerator()) + if (xmpp.isModerator()) { // TODO: Enable settings functionality. // Need to uncomment the settings button in index.html. @@ -2944,26 +3135,28 @@ var ToolbarToggler = { }; module.exports = ToolbarToggler; -},{}],17:[function(require,module,exports){ -/* global $, buttonClick, config, lockRoom, Moderator, roomName, - setSharedKey, sharedKey, Util */ +},{}],18:[function(require,module,exports){ +/* global $, buttonClick, config, lockRoom, + setSharedKey, Util */ var messageHandler = require("../util/MessageHandler"); var BottomToolbar = require("./BottomToolbar"); var Prezi = require("../prezi/Prezi"); var Etherpad = require("../etherpad/Etherpad"); var PanelToggler = require("../side_pannels/SidePanelToggler"); +var Authentication = require("../authentication/Authentication"); +var UIUtil = require("../util/UIUtil"); var roomUrl = null; var sharedKey = ''; -var authenticationWindow = null; +var UI = null; var buttonHandlers = { "toolbar_button_mute": function () { - return toggleAudio(); + return UI.toggleAudio(); }, "toolbar_button_camera": function () { - return toggleVideo(); + return UI.toggleVideo(); }, "toolbar_button_authentication": function () { return Toolbar.authenticateClicked(); @@ -2991,7 +3184,7 @@ var buttonHandlers = }, "toolbar_button_fullScreen": function() { - buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen"); + UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen"); return Toolbar.toggleFullScreen(); }, "toolbar_button_sip": function () { @@ -3006,9 +3199,7 @@ var buttonHandlers = }; function hangup() { - disposeConference(); - sessionTerminated = true; - connection.emuc.doLeave(); + xmpp.disposeConference(); if(config.enableWelcomePage) { setTimeout(function() @@ -3037,7 +3228,29 @@ function hangup() { */ function toggleRecording() { - Recording.toggleRecording(); + xmpp.toggleRecording(function (callback) { + UI.messageHandler.openTwoButtonDialog(null, + '

Enter recording token

' + + '', + false, + "Save", + function (e, v, m, f) { + if (v) { + var token = document.getElementById('recordingToken'); + + if (token.value) { + callback(Util.escapeHtml(token.value)); + } + } + }, + function (event) { + document.getElementById('recordingToken').focus(); + }, + function () { + } + ); + }, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState); } /** @@ -3048,7 +3261,7 @@ function lockRoom(lock) { if (lock) currentSharedKey = sharedKey; - connection.emuc.lockRoom(currentSharedKey, function (res) { + xmpp.lockRoom(currentSharedKey, function (res) { // password is required if (sharedKey) { @@ -3130,9 +3343,8 @@ function callSipButtonClicked() if (v) { var numberInput = document.getElementById('sipNumber'); if (numberInput.value) { - connection.rayo.dial( - numberInput.value, 'fromnumber', - roomName, sharedKey); + xmpp.dial(numberInput.value, 'fromnumber', + UI.getRoomName(), sharedKey); } } }, @@ -3144,9 +3356,10 @@ function callSipButtonClicked() var Toolbar = (function (my) { - my.init = function () { + my.init = function (ui) { for(var k in buttonHandlers) $("#" + k).click(buttonHandlers[k]); + UI = ui; } /** @@ -3157,35 +3370,15 @@ var Toolbar = (function (my) { sharedKey = sKey; }; - my.closeAuthenticationWindow = function () { - if (authenticationWindow) { - authenticationWindow.close(); - authenticationWindow = null; - } - } - my.authenticateClicked = function () { - // If auth window exists just bring it to the front - if (authenticationWindow) { - authenticationWindow.focus(); - return; - } + Authentication.focusAuthenticationWindow(); // Get authentication URL - Moderator.getAuthUrl(function (url) { + xmpp.getAuthUrl(UI.getRoomName(), function (url) { // Open popup with authentication URL - authenticationWindow = messageHandler.openCenteredPopup( - url, 910, 660, - // On closed - function () { - // Close authentication dialog if opened - if (authDialog) { - messageHandler.closeDialog(); - authDialog = null; - } - // On popup closed - retry room allocation - Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus); - authenticationWindow = null; - }); + var authenticationWindow = Authentication.createAuthenticationWindow(function () { + // On popup closed - retry room allocation + xmpp.allocateConferenceFocus(UI.getRoomName(), UI.checkForNicknameAndJoin); + }, url); if (!authenticationWindow) { Toolbar.showAuthenticateButton(true); messageHandler.openMessageDialog( @@ -3226,7 +3419,7 @@ var Toolbar = (function (my) { */ my.openLockDialog = function () { // Only the focus is able to set a shared key. - if (!Moderator.isModerator()) { + if (!xmpp.isModerator()) { if (sharedKey) { messageHandler.openMessageDialog(null, "This conversation is currently protected by" + @@ -3383,14 +3576,14 @@ var Toolbar = (function (my) { */ my.unlockLockButton = function () { if ($("#lockIcon").hasClass("icon-security-locked")) - buttonClick("#lockIcon", "icon-security icon-security-locked"); + UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked"); }; /** * Updates the lock button state to locked. */ my.lockLockButton = function () { if ($("#lockIcon").hasClass("icon-security")) - buttonClick("#lockIcon", "icon-security icon-security-locked"); + UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked"); }; /** @@ -3433,7 +3626,7 @@ var Toolbar = (function (my) { // Shows or hides SIP calls button my.showSipCallButton = function (show) { - if (Moderator.isSipGatewayEnabled() && show) { + if (xmpp.isSipGatewayEnabled() && show) { $('#sipCallButton').css({display: "inline"}); } else { $('#sipCallButton').css({display: "none"}); @@ -3461,9 +3654,9 @@ var Toolbar = (function (my) { }(Toolbar || {})); module.exports = Toolbar; -},{"../etherpad/Etherpad":5,"../prezi/Prezi":6,"../side_pannels/SidePanelToggler":7,"../util/MessageHandler":20,"./BottomToolbar":15}],18:[function(require,module,exports){ -module.exports=require(16) -},{}],19:[function(require,module,exports){ +},{"../authentication/Authentication":4,"../etherpad/Etherpad":6,"../prezi/Prezi":7,"../side_pannels/SidePanelToggler":8,"../util/MessageHandler":21,"../util/UIUtil":22,"./BottomToolbar":16}],19:[function(require,module,exports){ +module.exports=require(17) +},{}],20:[function(require,module,exports){ var JitsiPopover = (function () { /** * Constructs new JitsiPopover and attaches it to the element @@ -3587,7 +3780,7 @@ var JitsiPopover = (function () { })(); module.exports = JitsiPopover; -},{}],20:[function(require,module,exports){ +},{}],21:[function(require,module,exports){ /* global $, jQuery */ var messageHandler = (function(my) { @@ -3756,7 +3949,7 @@ module.exports = messageHandler; -},{}],21:[function(require,module,exports){ +},{}],22:[function(require,module,exports){ /** * Created by hristo on 12/22/14. */ @@ -3770,10 +3963,17 @@ module.exports = { = PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0; return window.innerWidth - rightPanelWidth; + }, + /** + * Changes the style class of the element given by id. + */ + buttonClick: function(id, classname) { + $(id).toggleClass(classname); // add the class to the clicked element } + }; -},{"../side_pannels/SidePanelToggler":7}],22:[function(require,module,exports){ +},{"../side_pannels/SidePanelToggler":8}],23:[function(require,module,exports){ var JitsiPopover = require("../util/JitsiPopover"); /** @@ -4184,7 +4384,7 @@ ConnectionIndicator.prototype.hideIndicator = function () { }; module.exports = ConnectionIndicator; -},{"../util/JitsiPopover":19}],23:[function(require,module,exports){ +},{"../util/JitsiPopover":20}],24:[function(require,module,exports){ var AudioLevels = require("../audio_levels/AudioLevels"); var Avatar = require("../avatar/Avatar"); var Chat = require("../side_pannels/chat/Chat"); @@ -4203,8 +4403,99 @@ var largeVideoState = { newSrc: '' }; +/** + * Indicates if we have muted our audio before the conference has started. + * @type {boolean} + */ +var preMuted = false; + +var mutedAudios = {}; + +var flipXLocalVideo = true; +var currentVideoWidth = null; +var currentVideoHeight = null; + +var localVideoSrc = null; + var defaultLocalDisplayName = "Me"; +function videoactive( videoelem) { + if (videoelem.attr('id').indexOf('mixedmslabel') === -1) { + // ignore mixedmslabela0 and v0 + + videoelem.show(); + VideoLayout.resizeThumbnails(); + + var videoParent = videoelem.parent(); + var parentResourceJid = null; + if (videoParent) + parentResourceJid + = VideoLayout.getPeerContainerResourceJid(videoParent[0]); + + // Update the large video to the last added video only if there's no + // current dominant, focused speaker or prezi playing or update it to + // the current dominant speaker. + if ((!focusedVideoInfo && + !VideoLayout.getDominantSpeakerResourceJid() && + !require("../prezi/Prezi").isPresentationVisible()) || + (parentResourceJid && + VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) { + VideoLayout.updateLargeVideo( + RTC.getVideoSrc(videoelem[0]), + 1, + parentResourceJid); + } + + VideoLayout.showModeratorIndicator(); + } +} + +function waitForRemoteVideo(selector, ssrc, stream, jid) { + // XXX(gp) so, every call to this function is *always* preceded by a call + // to the RTC.attachMediaStream() function but that call is *not* followed + // by an update to the videoSrcToSsrc map! + // + // The above way of doing things results in video SRCs that don't correspond + // to any SSRC for a short period of time (to be more precise, for as long + // the waitForRemoteVideo takes to complete). This causes problems (see + // bellow). + // + // I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream() + // a second time in here and only then update the videoSrcToSsrc map? Why + // not simply update the videoSrcToSsrc map when the RTC.attachMediaStream() + // is called the first time? I actually do that in the lastN changed event + // handler because the "orphan" video SRC is causing troubles there. The + // purpose of this method would then be to fire the "videoactive.jingle". + // + // Food for though I guess :-) + + if (selector.removed || !selector.parent().is(":visible")) { + console.warn("Media removed before had started", selector); + return; + } + + if (stream.id === 'mixedmslabel') return; + + if (selector[0].currentTime > 0) { + var videoStream = simulcast.getReceivingVideoStream(stream); + RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF? + + // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type + // in order to get rid of too many maps + if (ssrc && jid) { + jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc; + } else { + console.warn("No ssrc given for jid", jid); + } + + videoactive(selector); + } else { + setTimeout(function () { + waitForRemoteVideo(selector, ssrc, stream, jid); + }, 250); + } +} + /** * Returns an array of the video horizontal and vertical indents, * so that if fits its parent. @@ -4381,7 +4672,7 @@ function getParticipantContainer(resourceJid) if (!resourceJid) return null; - if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)) + if (resourceJid === xmpp.myResource()) return $("#localVideoContainer"); else return $("#participant_" + resourceJid); @@ -4457,7 +4748,8 @@ function addRemoteVideoMenu(jid, parentElement) { event.preventDefault(); } var isMute = mutedAudios[jid] == true; - connection.moderate.setMute(jid, !isMute); + xmpp.setMute(jid, !isMute); + popupmenuElement.setAttribute('style', 'display:none;'); if (isMute) { @@ -4479,7 +4771,7 @@ function addRemoteVideoMenu(jid, parentElement) { var ejectLinkItem = document.createElement('a'); ejectLinkItem.innerHTML = ejectIndicator + ' Kick out'; ejectLinkItem.onclick = function(){ - connection.moderate.eject(jid); + xmpp.eject(jid); popupmenuElement.setAttribute('style', 'display:none;'); }; @@ -4587,6 +4879,43 @@ function createModeratorIndicatorElement(parentElement) { } +/** + * Checks if video identified by given src is desktop stream. + * @param videoSrc eg. + * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395 + * @returns {boolean} + */ +function isVideoSrcDesktop(jid) { + // FIXME: fix this mapping mess... + // figure out if large video is desktop stream or just a camera + + if(!jid) + return false; + var isDesktop = false; + if (xmpp.myJid() && + xmpp.myResource() === jid) { + // local video + isDesktop = desktopsharing.isUsingScreenStream(); + } else { + // Do we have associations... + var videoSsrc = jid2Ssrc[jid]; + if (videoSsrc) { + var videoType = ssrc2videoType[videoSsrc]; + if (videoType) { + // Finally there... + isDesktop = videoType === 'screen'; + } else { + console.error("No video type for ssrc: " + videoSsrc); + } + } else { + console.error("No ssrc for jid: " + jid); + } + } + return isDesktop; +} + + + var VideoLayout = (function (my) { my.connectionIndicators = {}; @@ -4594,6 +4923,16 @@ var VideoLayout = (function (my) { my.getVideoSize = getCameraVideoSize; my.getVideoPosition = getCameraVideoPosition; + my.init = function () { + // Listen for large video size updates + document.getElementById('largeVideo') + .addEventListener('loadedmetadata', function (e) { + currentVideoWidth = this.videoWidth; + currentVideoHeight = this.videoHeight; + VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight); + }); + }; + my.isInLastN = function(resource) { return lastNCount < 0 // lastN is disabled, return true || (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true @@ -4609,7 +4948,10 @@ var VideoLayout = (function (my) { document.getElementById('localAudio').autoplay = true; document.getElementById('localAudio').volume = 0; if (preMuted) { - setAudioMuted(true); + if(!UI.setAudioMuted(true)) + { + preMuted = mute; + } preMuted = false; } }; @@ -4646,14 +4988,14 @@ var VideoLayout = (function (my) { VideoLayout.handleVideoThumbClicked( RTC.getVideoSrc(localVideo), false, - Strophe.getResourceFromJid(connection.emuc.myroomjid)); + xmpp.myResource()); }); $('#localVideoContainer').click(function (event) { event.stopPropagation(); VideoLayout.handleVideoThumbClicked( RTC.getVideoSrc(localVideo), false, - Strophe.getResourceFromJid(connection.emuc.myroomjid)); + xmpp.myResource()); }); // Add hover handler @@ -4683,11 +5025,8 @@ var VideoLayout = (function (my) { localVideoSrc = RTC.getVideoSrc(localVideo); - var myResourceJid = null; - if(connection.emuc.myroomjid) - { - myResourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid); - } + var myResourceJid = xmpp.myResource(); + VideoLayout.updateLargeVideo(localVideoSrc, 0, myResourceJid); @@ -4726,7 +5065,7 @@ var VideoLayout = (function (my) { { if(container.id == "localVideoWrapper") { - jid = Strophe.getResourceFromJid(connection.emuc.myroomjid); + jid = xmpp.myResource(); } else { @@ -4804,9 +5143,9 @@ var VideoLayout = (function (my) { largeVideoState.isVisible = $('#largeVideo').is(':visible'); largeVideoState.isDesktop = isVideoSrcDesktop(resourceJid); if(jid2Ssrc[largeVideoState.userResourceJid] || - (connection && connection.emuc.myroomjid && + (xmpp.myResource() && largeVideoState.userResourceJid === - Strophe.getResourceFromJid(connection.emuc.myroomjid))) { + xmpp.myResource())) { largeVideoState.oldResourceJid = largeVideoState.userResourceJid; } else { largeVideoState.oldResourceJid = null; @@ -4830,7 +5169,7 @@ var VideoLayout = (function (my) { var doUpdate = function () { Avatar.updateActiveSpeakerAvatarSrc( - connection.emuc.findJidFromResource( + xmpp.findJidFromResource( largeVideoState.userResourceJid)); if (!userChanged && largeVideoState.preload && @@ -4910,7 +5249,7 @@ var VideoLayout = (function (my) { if(userChanged) { Avatar.showUserAvatar( - connection.emuc.findJidFromResource( + xmpp.findJidFromResource( largeVideoState.oldResourceJid)); } @@ -4925,7 +5264,7 @@ var VideoLayout = (function (my) { } } else { Avatar.showUserAvatar( - connection.emuc.findJidFromResource( + xmpp.findJidFromResource( largeVideoState.userResourceJid)); } @@ -5064,7 +5403,7 @@ var VideoLayout = (function (my) { focusedVideoInfo = null; if(focusResourceJid) { Avatar.showUserAvatar( - connection.emuc.findJidFromResource(focusResourceJid)); + xmpp.findJidFromResource(focusResourceJid)); } } } @@ -5136,7 +5475,7 @@ var VideoLayout = (function (my) { // If the peerJid is null then this video span couldn't be directly // associated with a participant (this could happen in the case of prezi). - if (Moderator.isModerator() && peerJid !== null) + if (xmpp.isModerator() && peerJid !== null) addRemoteVideoMenu(peerJid, container); remotes.appendChild(container); @@ -5321,13 +5660,13 @@ var VideoLayout = (function (my) { if (state == 'show') { // peerContainer.css('-webkit-filter', ''); - var jid = connection.emuc.findJidFromResource(resourceJid); + var jid = xmpp.findJidFromResource(resourceJid); Avatar.showUserAvatar(jid, false); } else // if (state == 'avatar') { // peerContainer.css('-webkit-filter', 'grayscale(100%)'); - var jid = connection.emuc.findJidFromResource(resourceJid); + var jid = xmpp.findJidFromResource(resourceJid); Avatar.showUserAvatar(jid, true); } } @@ -5353,8 +5692,7 @@ var VideoLayout = (function (my) { if (name && nickname !== name) { nickname = name; window.localStorage.displayname = nickname; - connection.emuc.addDisplayNameToPresence(nickname); - connection.emuc.sendPresence(); + xmpp.addToPresence("displayName", nickname); Chat.setChatConversationMode(true); } @@ -5425,7 +5763,7 @@ var VideoLayout = (function (my) { */ my.showModeratorIndicator = function () { - var isModerator = Moderator.isModerator(); + var isModerator = xmpp.isModerator(); if (isModerator) { var indicatorSpan = $('#localVideoContainer .focusindicator'); @@ -5434,7 +5772,10 @@ var VideoLayout = (function (my) { createModeratorIndicatorElement(indicatorSpan[0]); } } - Object.keys(connection.emuc.members).forEach(function (jid) { + + var members = xmpp.getMembers(); + + Object.keys(members).forEach(function (jid) { if (Strophe.getResourceFromJid(jid) === 'focus') { // Skip server side focus @@ -5450,7 +5791,7 @@ var VideoLayout = (function (my) { return; } - var member = connection.emuc.members[jid]; + var member = members[jid]; if (member.role === 'moderator') { // Remove menu if peer is moderator @@ -5622,7 +5963,7 @@ var VideoLayout = (function (my) { var videoSpanId = null; var videoContainerId = null; if (resourceJid - === Strophe.getResourceFromJid(connection.emuc.myroomjid)) { + === xmpp.myResource()) { videoSpanId = 'localVideoWrapper'; videoContainerId = 'localVideoContainer'; } @@ -5665,7 +6006,7 @@ var VideoLayout = (function (my) { } Avatar.showUserAvatar( - connection.emuc.findJidFromResource(resourceJid)); + xmpp.findJidFromResource(resourceJid)); } }; @@ -5790,7 +6131,7 @@ var VideoLayout = (function (my) { lastNPickupJid = jid; $(document).trigger("pinnedendpointchanged", [jid]); } - } else if (jid == connection.emuc.myroomjid) { + } else if (jid == xmpp.myJid()) { $("#localVideoContainer").click(); } } @@ -5802,13 +6143,13 @@ var VideoLayout = (function (my) { $(document).bind('audiomuted.muc', function (event, jid, isMuted) { /* // FIXME: but focus can not mute in this case ? - check - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { // The local mute indicator is controlled locally return; }*/ var videoSpanId = null; - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { videoSpanId = 'localVideoContainer'; } else { VideoLayout.ensurePeerContainerExists(jid); @@ -5817,7 +6158,7 @@ var VideoLayout = (function (my) { mutedAudios[jid] = isMuted; - if (Moderator.isModerator()) { + if (xmpp.isModerator()) { VideoLayout.updateRemoteVideoMenu(jid, isMuted); } @@ -5835,7 +6176,7 @@ var VideoLayout = (function (my) { Avatar.showUserAvatar(jid, isMuted); var videoSpanId = null; - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { videoSpanId = 'localVideoContainer'; } else { VideoLayout.ensurePeerContainerExists(jid); @@ -5849,11 +6190,11 @@ var VideoLayout = (function (my) { /** * Display name changed. */ - $(document).bind('displaynamechanged', - function (event, jid, displayName, status) { + my.onDisplayNameChanged = + function (jid, displayName, status) { var name = null; if (jid === 'localVideoContainer' - || jid === connection.emuc.myroomjid) { + || jid === xmpp.myJid()) { name = nickname; setDisplayName('localVideoContainer', displayName); @@ -5867,10 +6208,10 @@ var VideoLayout = (function (my) { } if(jid === 'localVideoContainer') - jid = connection.emuc.myroomjid; + jid = xmpp.myJid(); if(!name || name != displayName) API.triggerEvent("displayNameChange",{jid: jid, displayname: displayName}); - }); + }; /** * On dominant speaker changed event. @@ -5878,7 +6219,7 @@ var VideoLayout = (function (my) { $(document).bind('dominantspeakerchanged', function (event, resourceJid) { // We ignore local user events. if (resourceJid - === Strophe.getResourceFromJid(connection.emuc.myroomjid)) + === xmpp.myResource()) return; // Update the current dominant speaker. @@ -6009,7 +6350,7 @@ var VideoLayout = (function (my) { if (!isVisible) { console.log("Add to last N", resourceJid); - var jid = connection.emuc.findJidFromResource(resourceJid); + var jid = xmpp.findJidFromResource(resourceJid); var mediaStream = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; var sel = $('#participant_' + resourceJid + '>video'); @@ -6042,7 +6383,7 @@ var VideoLayout = (function (my) { var resource, container, src; var myResource - = Strophe.getResourceFromJid(connection.emuc.myroomjid); + = xmpp.myResource(); // Find out which endpoint to show in the large video. for (var i = 0; i < lastNEndpoints.length; i++) { @@ -6066,37 +6407,6 @@ var VideoLayout = (function (my) { } }); - $(document).bind('videoactive.jingle', function (event, videoelem) { - if (videoelem.attr('id').indexOf('mixedmslabel') === -1) { - // ignore mixedmslabela0 and v0 - - videoelem.show(); - VideoLayout.resizeThumbnails(); - - var videoParent = videoelem.parent(); - var parentResourceJid = null; - if (videoParent) - parentResourceJid - = VideoLayout.getPeerContainerResourceJid(videoParent[0]); - - // Update the large video to the last added video only if there's no - // current dominant, focused speaker or prezi playing or update it to - // the current dominant speaker. - if ((!focusedVideoInfo && - !VideoLayout.getDominantSpeakerResourceJid() && - !require("../prezi/Prezi").isPresentationVisible()) || - (parentResourceJid && - VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) { - VideoLayout.updateLargeVideo( - RTC.getVideoSrc(videoelem[0]), - 1, - parentResourceJid); - } - - VideoLayout.showModeratorIndicator(); - } - }); - $(document).bind('simulcastlayerschanging', function (event, endpointSimulcastLayers) { endpointSimulcastLayers.forEach(function (esl) { @@ -6117,13 +6427,13 @@ var VideoLayout = (function (my) { // Get session and stream from primary ssrc. var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC); - var session = res.session; + var sid = res.sid; var electedStream = res.stream; - if (session && electedStream) { + if (sid && electedStream) { var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC); - console.info([esl, primarySSRC, msid, session, electedStream]); + console.info([esl, primarySSRC, msid, sid, electedStream]); var msidParts = msid.split(' '); @@ -6143,7 +6453,7 @@ var VideoLayout = (function (my) { } } else { - console.error('Could not find a stream or a session.', session, electedStream); + console.error('Could not find a stream or a session.', sid, electedStream); } }); }); @@ -6175,17 +6485,17 @@ var VideoLayout = (function (my) { // Get session and stream from primary ssrc. var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC); - var session = res.session; + var sid = res.sid; var electedStream = res.stream; - if (session && electedStream) { + if (sid && electedStream) { var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC); console.info('Switching simulcast substream.'); - console.info([esl, primarySSRC, msid, session, electedStream]); + console.info([esl, primarySSRC, msid, sid, electedStream]); var msidParts = msid.split(' '); - var selRemoteVideo = $(['#', 'remoteVideo_', session.sid, '_', msidParts[0]].join('')); + var selRemoteVideo = $(['#', 'remoteVideo_', sid, '_', msidParts[0]].join('')); var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC]) == largeVideoState.userResourceJid); @@ -6222,7 +6532,7 @@ var VideoLayout = (function (my) { } var videoId; - if(resource == Strophe.getResourceFromJid(connection.emuc.myroomjid)) + if(resource == xmpp.myResource()) { videoId = "localVideoContainer"; } @@ -6235,7 +6545,7 @@ var VideoLayout = (function (my) { connectionIndicator.updatePopoverData(); } else { - console.error('Could not find a stream or a session.', session, electedStream); + console.error('Could not find a stream or a sid.', sid, electedStream); } }); }); @@ -6250,8 +6560,8 @@ var VideoLayout = (function (my) { if(object.resolution !== null) { resolution = object.resolution; - object.resolution = resolution[connection.emuc.myroomjid]; - delete resolution[connection.emuc.myroomjid]; + object.resolution = resolution[xmpp.myJid()]; + delete resolution[xmpp.myJid()]; } updateStatsIndicator("localVideoContainer", percent, object); for(var jid in resolution) @@ -6312,7 +6622,7 @@ var VideoLayout = (function (my) { }(VideoLayout || {})); module.exports = VideoLayout; -},{"../audio_levels/AudioLevels":2,"../avatar/Avatar":4,"../etherpad/Etherpad":5,"../prezi/Prezi":6,"../side_pannels/chat/Chat":8,"../side_pannels/contactlist/ContactList":12,"../util/UIUtil":21,"./ConnectionIndicator":22}],24:[function(require,module,exports){ +},{"../audio_levels/AudioLevels":2,"../avatar/Avatar":5,"../etherpad/Etherpad":6,"../prezi/Prezi":7,"../side_pannels/chat/Chat":9,"../side_pannels/contactlist/ContactList":13,"../util/UIUtil":22,"./ConnectionIndicator":23}],25:[function(require,module,exports){ //var nouns = [ //]; var pluralNouns = [ @@ -6493,7 +6803,7 @@ var RoomNameGenerator = { module.exports = RoomNameGenerator; -},{}],25:[function(require,module,exports){ +},{}],26:[function(require,module,exports){ var animateTimeout, updateTimeout; var RoomNameGenerator = require("./RoomnameGenerator"); @@ -6597,5 +6907,6 @@ function setupWelcomePage() } module.exports = setupWelcomePage; -},{"./RoomnameGenerator":24}]},{},[1])(1) -}); \ No newline at end of file +},{"./RoomnameGenerator":25}]},{},[1])(1) +}); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi91c3IvbG9jYWwvbGliL25vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL1VJLmpzIiwiL1VzZXJzL2hyaXN0by9Eb2N1bWVudHMvd29ya3NwYWNlL2ppdHNpLW1lZXQvbW9kdWxlcy9VSS9hdWRpb19sZXZlbHMvQXVkaW9MZXZlbHMuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL2F1ZGlvX2xldmVscy9DYW52YXNVdGlscy5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMvVUkvYXV0aGVudGljYXRpb24vQXV0aGVudGljYXRpb24uanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL2F2YXRhci9BdmF0YXIuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL2V0aGVycGFkL0V0aGVycGFkLmpzIiwiL1VzZXJzL2hyaXN0by9Eb2N1bWVudHMvd29ya3NwYWNlL2ppdHNpLW1lZXQvbW9kdWxlcy9VSS9wcmV6aS9QcmV6aS5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMvVUkvc2lkZV9wYW5uZWxzL1NpZGVQYW5lbFRvZ2dsZXIuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL3NpZGVfcGFubmVscy9jaGF0L0NoYXQuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL3NpZGVfcGFubmVscy9jaGF0L0NvbW1hbmRzLmpzIiwiL1VzZXJzL2hyaXN0by9Eb2N1bWVudHMvd29ya3NwYWNlL2ppdHNpLW1lZXQvbW9kdWxlcy9VSS9zaWRlX3Bhbm5lbHMvY2hhdC9SZXBsYWNlbWVudC5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMvVUkvc2lkZV9wYW5uZWxzL2NoYXQvc21pbGV5cy5qc29uIiwiL1VzZXJzL2hyaXN0by9Eb2N1bWVudHMvd29ya3NwYWNlL2ppdHNpLW1lZXQvbW9kdWxlcy9VSS9zaWRlX3Bhbm5lbHMvY29udGFjdGxpc3QvQ29udGFjdExpc3QuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL3NpZGVfcGFubmVscy9zZXR0aW5ncy9TZXR0aW5ncy5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMvVUkvc2lkZV9wYW5uZWxzL3NldHRpbmdzL1NldHRpbmdzTWVudS5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMvVUkvdG9vbGJhcnMvQm90dG9tVG9vbGJhci5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMvVUkvdG9vbGJhcnMvVG9vbGJhclRvZ2dsZXIuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL3Rvb2xiYXJzL3Rvb2xiYXIuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL3V0aWwvSml0c2lQb3BvdmVyLmpzIiwiL1VzZXJzL2hyaXN0by9Eb2N1bWVudHMvd29ya3NwYWNlL2ppdHNpLW1lZXQvbW9kdWxlcy9VSS91dGlsL01lc3NhZ2VIYW5kbGVyLmpzIiwiL1VzZXJzL2hyaXN0by9Eb2N1bWVudHMvd29ya3NwYWNlL2ppdHNpLW1lZXQvbW9kdWxlcy9VSS91dGlsL1VJVXRpbC5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMvVUkvdmlkZW9sYXlvdXQvQ29ubmVjdGlvbkluZGljYXRvci5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMvVUkvdmlkZW9sYXlvdXQvVmlkZW9MYXlvdXQuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL1VJL3dlbGNvbWVfcGFnZS9Sb29tbmFtZUdlbmVyYXRvci5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMvVUkvd2VsY29tZV9wYWdlL1dlbGNvbWVQYWdlLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDeHJCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdlFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM5R0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25GQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6SkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2xNQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOVZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9QQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3RXQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzlGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOURBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2hEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdExBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMURBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6Q0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2pIQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7O0FDcmdCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUhBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2S0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN0QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6WkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1ckVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuTEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwidmFyIFVJID0ge307XG5cbnZhciBWaWRlb0xheW91dCA9IHJlcXVpcmUoXCIuL3ZpZGVvbGF5b3V0L1ZpZGVvTGF5b3V0LmpzXCIpO1xudmFyIEF1ZGlvTGV2ZWxzID0gcmVxdWlyZShcIi4vYXVkaW9fbGV2ZWxzL0F1ZGlvTGV2ZWxzLmpzXCIpO1xudmFyIFByZXppID0gcmVxdWlyZShcIi4vcHJlemkvUHJlemkuanNcIik7XG52YXIgRXRoZXJwYWQgPSByZXF1aXJlKFwiLi9ldGhlcnBhZC9FdGhlcnBhZC5qc1wiKTtcbnZhciBDaGF0ID0gcmVxdWlyZShcIi4vc2lkZV9wYW5uZWxzL2NoYXQvQ2hhdC5qc1wiKTtcbnZhciBUb29sYmFyID0gcmVxdWlyZShcIi4vdG9vbGJhcnMvdG9vbGJhclwiKTtcbnZhciBUb29sYmFyVG9nZ2xlciA9IHJlcXVpcmUoXCIuL3Rvb2xiYXJzL3Rvb2xiYXJ0b2dnbGVyXCIpO1xudmFyIEJvdHRvbVRvb2xiYXIgPSByZXF1aXJlKFwiLi90b29sYmFycy9Cb3R0b21Ub29sYmFyXCIpO1xudmFyIENvbnRhY3RMaXN0ID0gcmVxdWlyZShcIi4vc2lkZV9wYW5uZWxzL2NvbnRhY3RsaXN0L0NvbnRhY3RMaXN0XCIpO1xudmFyIEF2YXRhciA9IHJlcXVpcmUoXCIuL2F2YXRhci9BdmF0YXJcIik7XG4vL3ZhciBFdmVudEVtaXR0ZXIgPSByZXF1aXJlKFwiZXZlbnRzXCIpO1xudmFyIFNldHRpbmdzTWVudSA9IHJlcXVpcmUoXCIuL3NpZGVfcGFubmVscy9zZXR0aW5ncy9TZXR0aW5nc01lbnVcIik7XG52YXIgU2V0dGluZ3MgPSByZXF1aXJlKFwiLi9zaWRlX3Bhbm5lbHMvc2V0dGluZ3MvU2V0dGluZ3NcIik7XG52YXIgUGFuZWxUb2dnbGVyID0gcmVxdWlyZShcIi4vc2lkZV9wYW5uZWxzL1NpZGVQYW5lbFRvZ2dsZXJcIik7XG52YXIgUm9vbU5hbWVHZW5lcmF0b3IgPSByZXF1aXJlKFwiLi93ZWxjb21lX3BhZ2UvUm9vbW5hbWVHZW5lcmF0b3JcIik7XG5VSS5tZXNzYWdlSGFuZGxlciA9IHJlcXVpcmUoXCIuL3V0aWwvTWVzc2FnZUhhbmRsZXJcIik7XG52YXIgbWVzc2FnZUhhbmRsZXIgPSBVSS5tZXNzYWdlSGFuZGxlcjtcbnZhciBBdXRoZW50aWNhdGlvbiAgPSByZXF1aXJlKFwiLi9hdXRoZW50aWNhdGlvbi9BdXRoZW50aWNhdGlvblwiKTtcbnZhciBVSVV0aWwgPSByZXF1aXJlKFwiLi91dGlsL1VJVXRpbFwiKTtcblxuLy92YXIgZXZlbnRFbWl0dGVyID0gbmV3IEV2ZW50RW1pdHRlcigpO1xudmFyIHJvb21OYW1lID0gbnVsbDtcblxuXG5mdW5jdGlvbiBzZXR1cFByZXppKClcbntcbiAgICAkKFwiI3JlbG9hZFByZXNlbnRhdGlvbkxpbmtcIikuY2xpY2soZnVuY3Rpb24oKVxuICAgIHtcbiAgICAgICAgUHJlemkucmVsb2FkUHJlc2VudGF0aW9uKCk7XG4gICAgfSk7XG59XG5cbmZ1bmN0aW9uIHNldHVwQ2hhdCgpXG57XG4gICAgQ2hhdC5pbml0KCk7XG4gICAgJChcIiN0b2dnbGVfc21pbGV5c1wiKS5jbGljayhmdW5jdGlvbigpIHtcbiAgICAgICAgQ2hhdC50b2dnbGVTbWlsZXlzKCk7XG4gICAgfSk7XG59XG5cbmZ1bmN0aW9uIHNldHVwVG9vbGJhcnMoKSB7XG4gICAgVG9vbGJhci5pbml0KFVJKTtcbiAgICBUb29sYmFyLnNldHVwQnV0dG9uc0Zyb21Db25maWcoKTtcbiAgICBCb3R0b21Ub29sYmFyLmluaXQoKTtcbn1cblxuZnVuY3Rpb24gc3RyZWFtSGFuZGxlcihzdHJlYW0pIHtcbiAgICBzd2l0Y2ggKHN0cmVhbS50eXBlKVxuICAgIHtcbiAgICAgICAgY2FzZSBcImF1ZGlvXCI6XG4gICAgICAgICAgICBWaWRlb0xheW91dC5jaGFuZ2VMb2NhbEF1ZGlvKHN0cmVhbSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSBcInZpZGVvXCI6XG4gICAgICAgICAgICBWaWRlb0xheW91dC5jaGFuZ2VMb2NhbFZpZGVvKHN0cmVhbSk7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSBcInN0cmVhbVwiOlxuICAgICAgICAgICAgVmlkZW9MYXlvdXQuY2hhbmdlTG9jYWxTdHJlYW0oc3RyZWFtKTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlIFwiZGVza3RvcFwiOlxuICAgICAgICAgICAgVmlkZW9MYXlvdXQuY2hhbmdlTG9jYWxWaWRlbyhzdHJlYW0pO1xuICAgICAgICAgICAgYnJlYWs7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBvbkRpc3Bvc2VDb25mZXJlbmNlKHVubG9hZCkge1xuICAgIFRvb2xiYXIuc2hvd0F1dGhlbnRpY2F0ZUJ1dHRvbihmYWxzZSk7XG59O1xuXG5mdW5jdGlvbiBvbkRpc3BsYXlOYW1lQ2hhbmdlZChqaWQsIGRpc3BsYXlOYW1lKSB7XG4gICAgQ29udGFjdExpc3Qub25EaXNwbGF5TmFtZUNoYW5nZShqaWQsIGRpc3BsYXlOYW1lKTtcbiAgICBTZXR0aW5nc01lbnUub25EaXNwbGF5TmFtZUNoYW5nZShqaWQsIGRpc3BsYXlOYW1lKTtcbiAgICBWaWRlb0xheW91dC5vbkRpc3BsYXlOYW1lQ2hhbmdlZChqaWQsIGRpc3BsYXlOYW1lKTtcbn1cblxuZnVuY3Rpb24gcmVnaXN0ZXJMaXN0ZW5lcnMoKSB7XG4gICAgUlRDLmFkZFN0cmVhbUxpc3RlbmVyKHN0cmVhbUhhbmRsZXIsIFN0cmVhbUV2ZW50VHlwZXMuRVZFTlRfVFlQRV9MT0NBTF9DUkVBVEVEKTtcblxuICAgIFJUQy5hZGRTdHJlYW1MaXN0ZW5lcihzdHJlYW1IYW5kbGVyLCBTdHJlYW1FdmVudFR5cGVzLkVWRU5UX1RZUEVfTE9DQUxfQ0hBTkdFRCk7XG4gICAgUlRDLmFkZFN0cmVhbUxpc3RlbmVyKGZ1bmN0aW9uIChzdHJlYW0pIHtcbiAgICAgICAgVmlkZW9MYXlvdXQub25SZW1vdGVTdHJlYW1BZGRlZChzdHJlYW0pO1xuICAgIH0sIFN0cmVhbUV2ZW50VHlwZXMuRVZFTlRfVFlQRV9SRU1PVEVfQ1JFQVRFRCk7XG5cbiAgICBWaWRlb0xheW91dC5pbml0KCk7XG5cbiAgICBzdGF0aXN0aWNzLmFkZEF1ZGlvTGV2ZWxMaXN0ZW5lcihmdW5jdGlvbihqaWQsIGF1ZGlvTGV2ZWwpXG4gICAge1xuICAgICAgICB2YXIgcmVzb3VyY2VKaWQ7XG4gICAgICAgIGlmKGppZCA9PT0gc3RhdGlzdGljcy5MT0NBTF9KSUQpXG4gICAgICAgIHtcbiAgICAgICAgICAgIHJlc291cmNlSmlkID0gQXVkaW9MZXZlbHMuTE9DQUxfTEVWRUw7XG4gICAgICAgICAgICBpZihSVEMubG9jYWxBdWRpby5pc011dGVkKCkpXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgYXVkaW9MZXZlbCA9IDA7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZVxuICAgICAgICB7XG4gICAgICAgICAgICByZXNvdXJjZUppZCA9IFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCk7XG4gICAgICAgIH1cblxuICAgICAgICBBdWRpb0xldmVscy51cGRhdGVBdWRpb0xldmVsKHJlc291cmNlSmlkLCBhdWRpb0xldmVsLFxuICAgICAgICAgICAgVUkuZ2V0TGFyZ2VWaWRlb1N0YXRlKCkudXNlclJlc291cmNlSmlkKTtcbiAgICB9KTtcbiAgICBkZXNrdG9wc2hhcmluZy5hZGRMaXN0ZW5lcihmdW5jdGlvbiAoKSB7XG4gICAgICAgIFRvb2xiYXJUb2dnbGVyLnNob3dEZXNrdG9wU2hhcmluZ0J1dHRvbigpO1xuICAgIH0sIERlc2t0b3BTaGFyaW5nRXZlbnRUeXBlcy5JTklUKTtcbiAgICBkZXNrdG9wc2hhcmluZy5hZGRMaXN0ZW5lcihcbiAgICAgICAgVG9vbGJhci5jaGFuZ2VEZXNrdG9wU2hhcmluZ0J1dHRvblN0YXRlLFxuICAgICAgICBEZXNrdG9wU2hhcmluZ0V2ZW50VHlwZXMuU1dJVENISU5HX0RPTkUpO1xuICAgIHhtcHAuYWRkTGlzdGVuZXIoWE1QUEV2ZW50cy5ESVNQT1NFX0NPTkZFUkVOQ0UsIG9uRGlzcG9zZUNvbmZlcmVuY2UpO1xuICAgIHhtcHAuYWRkTGlzdGVuZXIoWE1QUEV2ZW50cy5LSUNLRUQsIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgbWVzc2FnZUhhbmRsZXIub3Blbk1lc3NhZ2VEaWFsb2coXCJTZXNzaW9uIFRlcm1pbmF0ZWRcIixcbiAgICAgICAgICAgIFwiT3VjaCEgWW91IGhhdmUgYmVlbiBraWNrZWQgb3V0IG9mIHRoZSBtZWV0IVwiKTtcbiAgICB9KTtcbiAgICB4bXBwLmFkZExpc3RlbmVyKFhNUFBFdmVudHMuQlJJREdFX0RPV04sIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgbWVzc2FnZUhhbmRsZXIuc2hvd0Vycm9yKFwiRXJyb3JcIixcbiAgICAgICAgICAgIFwiSml0c2kgVmlkZW9icmlkZ2UgaXMgY3VycmVudGx5IHVuYXZhaWxhYmxlLiBQbGVhc2UgdHJ5IGFnYWluIGxhdGVyIVwiKTtcbiAgICB9KTtcbiAgICB4bXBwLmFkZExpc3RlbmVyKFhNUFBFdmVudHMuVVNFUl9JRF9DSEFOR0VELCBBdmF0YXIuc2V0VXNlckF2YXRhcik7XG4gICAgeG1wcC5hZGRMaXN0ZW5lcihYTVBQRXZlbnRzLkNIQU5HRURfU1RSRUFNUywgZnVuY3Rpb24gKGppZCwgY2hhbmdlZFN0cmVhbXMpIHtcbiAgICAgICAgZm9yKHN0cmVhbSBpbiBjaGFuZ2VkU3RyZWFtcylcbiAgICAgICAge1xuICAgICAgICAgICAgLy8gbWlnaHQgbmVlZCB0byB1cGRhdGUgdGhlIGRpcmVjdGlvbiBpZiBwYXJ0aWNpcGFudCBqdXN0IHdlbnQgZnJvbSBzZW5kcmVjdiB0byByZWN2b25seVxuICAgICAgICAgICAgaWYgKHN0cmVhbS50eXBlID09PSAndmlkZW8nIHx8IHN0cmVhbS50eXBlID09PSAnc2NyZWVuJykge1xuICAgICAgICAgICAgICAgIHZhciBlbCA9ICQoJyNwYXJ0aWNpcGFudF8nICArIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCkgKyAnPnZpZGVvJyk7XG4gICAgICAgICAgICAgICAgc3dpdGNoIChzdHJlYW0uZGlyZWN0aW9uKSB7XG4gICAgICAgICAgICAgICAgICAgIGNhc2UgJ3NlbmRyZWN2JzpcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsLnNob3coKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICBjYXNlICdyZWN2b25seSc6XG4gICAgICAgICAgICAgICAgICAgICAgICBlbC5oaWRlKCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBGSVhNRTogQ2hlY2sgaWYgd2UgaGF2ZSB0byBjaGFuZ2UgbGFyZ2UgdmlkZW9cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vVmlkZW9MYXlvdXQudXBkYXRlTGFyZ2VWaWRlbyhlbCk7XG4gICAgICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgIH0pO1xuICAgIHhtcHAuYWRkTGlzdGVuZXIoWE1QUEV2ZW50cy5ESVNQTEFZX05BTUVfQ0hBTkdFRCwgb25EaXNwbGF5TmFtZUNoYW5nZWQpO1xuICAgIHhtcHAuYWRkTGlzdGVuZXIoWE1QUEV2ZW50cy5NVUNfSk9JTkVELCBvbk11Y0pvaW5lZCk7XG59XG5cbmZ1bmN0aW9uIGJpbmRFdmVudHMoKVxue1xuICAgIC8qKlxuICAgICAqIFJlc2l6ZXMgYW5kIHJlcG9zaXRpb25zIHZpZGVvcyBpbiBmdWxsIHNjcmVlbiBtb2RlLlxuICAgICAqL1xuICAgICQoZG9jdW1lbnQpLm9uKCd3ZWJraXRmdWxsc2NyZWVuY2hhbmdlIG1vemZ1bGxzY3JlZW5jaGFuZ2UgZnVsbHNjcmVlbmNoYW5nZScsXG4gICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIFZpZGVvTGF5b3V0LnJlc2l6ZUxhcmdlVmlkZW9Db250YWluZXIoKTtcbiAgICAgICAgICAgIFZpZGVvTGF5b3V0LnBvc2l0aW9uTGFyZ2UoKTtcbiAgICAgICAgfVxuICAgICk7XG5cbiAgICAkKHdpbmRvdykucmVzaXplKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgVmlkZW9MYXlvdXQucmVzaXplTGFyZ2VWaWRlb0NvbnRhaW5lcigpO1xuICAgICAgICBWaWRlb0xheW91dC5wb3NpdGlvbkxhcmdlKCk7XG4gICAgfSk7XG59XG5cblVJLnN0YXJ0ID0gZnVuY3Rpb24gKCkge1xuICAgIGRvY3VtZW50LnRpdGxlID0gaW50ZXJmYWNlQ29uZmlnLkFQUF9OQU1FO1xuICAgIGlmKGNvbmZpZy5lbmFibGVXZWxjb21lUGFnZSAmJiB3aW5kb3cubG9jYXRpb24ucGF0aG5hbWUgPT0gXCIvXCIgJiZcbiAgICAgICAgKCF3aW5kb3cubG9jYWxTdG9yYWdlLndlbGNvbWVQYWdlRGlzYWJsZWQgfHwgd2luZG93LmxvY2FsU3RvcmFnZS53ZWxjb21lUGFnZURpc2FibGVkID09IFwiZmFsc2VcIikpXG4gICAge1xuICAgICAgICAkKFwiI3ZpZGVvY29uZmVyZW5jZV9wYWdlXCIpLmhpZGUoKTtcbiAgICAgICAgdmFyIHNldHVwV2VsY29tZVBhZ2UgPSByZXF1aXJlKFwiLi93ZWxjb21lX3BhZ2UvV2VsY29tZVBhZ2VcIik7XG4gICAgICAgIHNldHVwV2VsY29tZVBhZ2UoKTtcblxuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKGludGVyZmFjZUNvbmZpZy5TSE9XX0pJVFNJX1dBVEVSTUFSSykge1xuICAgICAgICB2YXIgbGVmdFdhdGVybWFya0RpdlxuICAgICAgICAgICAgPSAkKFwiI2xhcmdlVmlkZW9Db250YWluZXIgZGl2W2NsYXNzPSd3YXRlcm1hcmsgbGVmdHdhdGVybWFyayddXCIpO1xuXG4gICAgICAgIGxlZnRXYXRlcm1hcmtEaXYuY3NzKHtkaXNwbGF5OiAnYmxvY2snfSk7XG4gICAgICAgIGxlZnRXYXRlcm1hcmtEaXYucGFyZW50KCkuZ2V0KDApLmhyZWZcbiAgICAgICAgICAgID0gaW50ZXJmYWNlQ29uZmlnLkpJVFNJX1dBVEVSTUFSS19MSU5LO1xuICAgIH1cblxuICAgIGlmIChpbnRlcmZhY2VDb25maWcuU0hPV19CUkFORF9XQVRFUk1BUkspIHtcbiAgICAgICAgdmFyIHJpZ2h0V2F0ZXJtYXJrRGl2XG4gICAgICAgICAgICA9ICQoXCIjbGFyZ2VWaWRlb0NvbnRhaW5lciBkaXZbY2xhc3M9J3dhdGVybWFyayByaWdodHdhdGVybWFyayddXCIpO1xuXG4gICAgICAgIHJpZ2h0V2F0ZXJtYXJrRGl2LmNzcyh7ZGlzcGxheTogJ2Jsb2NrJ30pO1xuICAgICAgICByaWdodFdhdGVybWFya0Rpdi5wYXJlbnQoKS5nZXQoMCkuaHJlZlxuICAgICAgICAgICAgPSBpbnRlcmZhY2VDb25maWcuQlJBTkRfV0FURVJNQVJLX0xJTks7XG4gICAgICAgIHJpZ2h0V2F0ZXJtYXJrRGl2LmdldCgwKS5zdHlsZS5iYWNrZ3JvdW5kSW1hZ2VcbiAgICAgICAgICAgID0gXCJ1cmwoaW1hZ2VzL3JpZ2h0d2F0ZXJtYXJrLnBuZylcIjtcbiAgICB9XG5cbiAgICBpZiAoaW50ZXJmYWNlQ29uZmlnLlNIT1dfUE9XRVJFRF9CWSkge1xuICAgICAgICAkKFwiI2xhcmdlVmlkZW9Db250YWluZXI+YVtjbGFzcz0ncG93ZXJlZGJ5J11cIikuY3NzKHtkaXNwbGF5OiAnYmxvY2snfSk7XG4gICAgfVxuXG4gICAgJChcIiN3ZWxjb21lX3BhZ2VcIikuaGlkZSgpO1xuXG4gICAgJCgnYm9keScpLnBvcG92ZXIoeyBzZWxlY3RvcjogJ1tkYXRhLXRvZ2dsZT1wb3BvdmVyXScsXG4gICAgICAgIHRyaWdnZXI6ICdjbGljayBob3ZlcicsXG4gICAgICAgIGNvbnRlbnQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuZ2V0QXR0cmlidXRlKFwiY29udGVudFwiKSArXG4gICAgICAgICAgICAgICAgS2V5Ym9hcmRTaG9ydGN1dC5nZXRTaG9ydGN1dCh0aGlzLmdldEF0dHJpYnV0ZShcInNob3J0Y3V0XCIpKTtcbiAgICAgICAgfVxuICAgIH0pO1xuICAgIFZpZGVvTGF5b3V0LnJlc2l6ZUxhcmdlVmlkZW9Db250YWluZXIoKTtcbiAgICAkKFwiI3ZpZGVvc3BhY2VcIikubW91c2Vtb3ZlKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIFRvb2xiYXJUb2dnbGVyLnNob3dUb29sYmFyKCk7XG4gICAgfSk7XG4gICAgLy8gU2V0IHRoZSBkZWZhdWx0cyBmb3IgcHJvbXB0IGRpYWxvZ3MuXG4gICAgalF1ZXJ5LnByb21wdC5zZXREZWZhdWx0cyh7cGVyc2lzdGVudDogZmFsc2V9KTtcblxuLy8gICAgS2V5Ym9hcmRTaG9ydGN1dC5pbml0KCk7XG4gICAgcmVnaXN0ZXJMaXN0ZW5lcnMoKTtcbiAgICBiaW5kRXZlbnRzKCk7XG4gICAgc2V0dXBQcmV6aSgpO1xuICAgIHNldHVwVG9vbGJhcnMoKTtcbiAgICBzZXR1cENoYXQoKTtcblxuICAgIGRvY3VtZW50LnRpdGxlID0gaW50ZXJmYWNlQ29uZmlnLkFQUF9OQU1FO1xuXG4gICAgJChcIiNkb3dubG9hZGxvZ1wiKS5jbGljayhmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgZHVtcChldmVudC50YXJnZXQpO1xuICAgIH0pO1xuXG4gICAgaWYoY29uZmlnLmVuYWJsZVdlbGNvbWVQYWdlICYmIHdpbmRvdy5sb2NhdGlvbi5wYXRobmFtZSA9PSBcIi9cIiAmJlxuICAgICAgICAoIXdpbmRvdy5sb2NhbFN0b3JhZ2Uud2VsY29tZVBhZ2VEaXNhYmxlZCB8fCB3aW5kb3cubG9jYWxTdG9yYWdlLndlbGNvbWVQYWdlRGlzYWJsZWQgPT0gXCJmYWxzZVwiKSlcbiAgICB7XG4gICAgICAgICQoXCIjdmlkZW9jb25mZXJlbmNlX3BhZ2VcIikuaGlkZSgpO1xuICAgICAgICB2YXIgc2V0dXBXZWxjb21lUGFnZSA9IHJlcXVpcmUoXCIuL3dlbGNvbWVfcGFnZS9XZWxjb21lUGFnZVwiKTtcbiAgICAgICAgc2V0dXBXZWxjb21lUGFnZSgpO1xuXG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAkKFwiI3dlbGNvbWVfcGFnZVwiKS5oaWRlKCk7XG5cbiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFyZ2VWaWRlbycpLnZvbHVtZSA9IDA7XG5cbiAgICBpZiAoISQoJyNzZXR0aW5ncycpLmlzKCc6dmlzaWJsZScpKSB7XG4gICAgICAgIGNvbnNvbGUubG9nKCdpbml0Jyk7XG4gICAgICAgIGluaXQoKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBsb2dpbkluZm8ub25zdWJtaXQgPSBmdW5jdGlvbiAoZSkge1xuICAgICAgICAgICAgaWYgKGUucHJldmVudERlZmF1bHQpIGUucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgICQoJyNzZXR0aW5ncycpLmhpZGUoKTtcbiAgICAgICAgICAgIGluaXQoKTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICB0b2FzdHIub3B0aW9ucyA9IHtcbiAgICAgICAgXCJjbG9zZUJ1dHRvblwiOiB0cnVlLFxuICAgICAgICBcImRlYnVnXCI6IGZhbHNlLFxuICAgICAgICBcInBvc2l0aW9uQ2xhc3NcIjogXCJub3RpZmljYXRpb24tYm90dG9tLXJpZ2h0XCIsXG4gICAgICAgIFwib25jbGlja1wiOiBudWxsLFxuICAgICAgICBcInNob3dEdXJhdGlvblwiOiBcIjMwMFwiLFxuICAgICAgICBcImhpZGVEdXJhdGlvblwiOiBcIjEwMDBcIixcbiAgICAgICAgXCJ0aW1lT3V0XCI6IFwiMjAwMFwiLFxuICAgICAgICBcImV4dGVuZGVkVGltZU91dFwiOiBcIjEwMDBcIixcbiAgICAgICAgXCJzaG93RWFzaW5nXCI6IFwic3dpbmdcIixcbiAgICAgICAgXCJoaWRlRWFzaW5nXCI6IFwibGluZWFyXCIsXG4gICAgICAgIFwic2hvd01ldGhvZFwiOiBcImZhZGVJblwiLFxuICAgICAgICBcImhpZGVNZXRob2RcIjogXCJmYWRlT3V0XCIsXG4gICAgICAgIFwicmVwb3NpdGlvblwiOiBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgIGlmKFBhbmVsVG9nZ2xlci5pc1Zpc2libGUoKSkge1xuICAgICAgICAgICAgICAgICQoXCIjdG9hc3QtY29udGFpbmVyXCIpLmFkZENsYXNzKFwibm90aWZpY2F0aW9uLWJvdHRvbS1yaWdodC1jZW50ZXJcIik7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICQoXCIjdG9hc3QtY29udGFpbmVyXCIpLnJlbW92ZUNsYXNzKFwibm90aWZpY2F0aW9uLWJvdHRvbS1yaWdodC1jZW50ZXJcIik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIFwibmV3ZXN0T25Ub3BcIjogZmFsc2VcbiAgICB9O1xuXG4gICAgJCgnI3NldHRpbmdzbWVudT5pbnB1dCcpLmtleXVwKGZ1bmN0aW9uKGV2ZW50KXtcbiAgICAgICAgaWYoZXZlbnQua2V5Q29kZSA9PT0gMTMpIHsvL2VudGVyXG4gICAgICAgICAgICBTZXR0aW5nc01lbnUudXBkYXRlKCk7XG4gICAgICAgIH1cbiAgICB9KTtcblxuICAgICQoXCIjdXBkYXRlU2V0dGluZ3NcIikuY2xpY2soZnVuY3Rpb24gKCkge1xuICAgICAgICBTZXR0aW5nc01lbnUudXBkYXRlKCk7XG4gICAgfSk7XG5cbn07XG5cblVJLnRvZ2dsZVNtaWxleXMgPSBmdW5jdGlvbiAoKSB7XG4gICAgQ2hhdC50b2dnbGVTbWlsZXlzKCk7XG59O1xuXG5VSS5jaGF0QWRkRXJyb3IgPSBmdW5jdGlvbihlcnJvck1lc3NhZ2UsIG9yaWdpbmFsVGV4dClcbntcbiAgICByZXR1cm4gQ2hhdC5jaGF0QWRkRXJyb3IoZXJyb3JNZXNzYWdlLCBvcmlnaW5hbFRleHQpO1xufTtcblxuVUkuY2hhdFNldFN1YmplY3QgPSBmdW5jdGlvbih0ZXh0KVxue1xuICAgIHJldHVybiBDaGF0LmNoYXRTZXRTdWJqZWN0KHRleHQpO1xufTtcblxuVUkudXBkYXRlQ2hhdENvbnZlcnNhdGlvbiA9IGZ1bmN0aW9uIChmcm9tLCBkaXNwbGF5TmFtZSwgbWVzc2FnZSkge1xuICAgIHJldHVybiBDaGF0LnVwZGF0ZUNoYXRDb252ZXJzYXRpb24oZnJvbSwgZGlzcGxheU5hbWUsIG1lc3NhZ2UpO1xufTtcblxuZnVuY3Rpb24gb25NdWNKb2luZWQoamlkLCBpbmZvKSB7XG4gICAgVG9vbGJhci51cGRhdGVSb29tVXJsKHdpbmRvdy5sb2NhdGlvbi5ocmVmKTtcbiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9jYWxOaWNrJykuYXBwZW5kQ2hpbGQoXG4gICAgICAgIGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCkgKyAnIChtZSknKVxuICAgICk7XG5cbiAgICB2YXIgc2V0dGluZ3MgPSBTZXR0aW5ncy5nZXRTZXR0aW5ncygpO1xuICAgIC8vIEFkZCBteXNlbGYgdG8gdGhlIGNvbnRhY3QgbGlzdC5cbiAgICBDb250YWN0TGlzdC5hZGRDb250YWN0KGppZCwgc2V0dGluZ3MuZW1haWwgfHwgc2V0dGluZ3MudWlkKTtcblxuICAgIC8vIE9uY2Ugd2UndmUgam9pbmVkIHRoZSBtdWMgc2hvdyB0aGUgdG9vbGJhclxuICAgIFRvb2xiYXJUb2dnbGVyLnNob3dUb29sYmFyKCk7XG5cbiAgICAvLyBTaG93IGF1dGhlbnRpY2F0ZSBidXR0b24gaWYgbmVlZGVkXG4gICAgVG9vbGJhci5zaG93QXV0aGVudGljYXRlQnV0dG9uKFxuICAgICAgICAgICAgeG1wcC5pc0V4dGVybmFsQXV0aEVuYWJsZWQoKSAmJiAheG1wcC5pc01vZGVyYXRvcigpKTtcblxuICAgIHZhciBkaXNwbGF5TmFtZSA9ICFjb25maWcuZGlzcGxheUppZHNcbiAgICAgICAgPyBpbmZvLmRpc3BsYXlOYW1lIDogU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKTtcblxuICAgIGlmIChkaXNwbGF5TmFtZSlcbiAgICAgICAgb25EaXNwbGF5TmFtZUNoYW5nZWQoJ2xvY2FsVmlkZW9Db250YWluZXInLCBkaXNwbGF5TmFtZSArICcgKG1lKScpO1xufVxuXG5VSS5pbml0RXRoZXJwYWQgPSBmdW5jdGlvbiAobmFtZSkge1xuICAgIEV0aGVycGFkLmluaXQobmFtZSk7XG59O1xuXG5VSS5vbk11Y0xlZnQgPSBmdW5jdGlvbiAoamlkKSB7XG4gICAgY29uc29sZS5sb2coJ2xlZnQubXVjJywgamlkKTtcbiAgICB2YXIgZGlzcGxheU5hbWUgPSAkKCcjcGFydGljaXBhbnRfJyArIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCkgK1xuICAgICAgICAnPi5kaXNwbGF5bmFtZScpLmh0bWwoKTtcbiAgICBtZXNzYWdlSGFuZGxlci5ub3RpZnkoZGlzcGxheU5hbWUgfHwgJ1NvbWVib2R5JyxcbiAgICAgICAgJ2Rpc2Nvbm5lY3RlZCcsXG4gICAgICAgICdkaXNjb25uZWN0ZWQnKTtcbiAgICAvLyBOZWVkIHRvIGNhbGwgdGhpcyB3aXRoIGEgc2xpZ2h0IGRlbGF5LCBvdGhlcndpc2UgdGhlIGVsZW1lbnQgY291bGRuJ3QgYmVcbiAgICAvLyBmb3VuZCBmb3Igc29tZSByZWFzb24uXG4gICAgLy8gWFhYKGdwKSBpdCB3b3JrcyBmaW5lIHdpdGhvdXQgdGhlIHRpbWVvdXQgZm9yIG1lICh3aXRoIENocm9tZSAzOCkuXG4gICAgd2luZG93LnNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgY29udGFpbmVyID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoXG4gICAgICAgICAgICAgICAgJ3BhcnRpY2lwYW50XycgKyBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChqaWQpKTtcbiAgICAgICAgaWYgKGNvbnRhaW5lcikge1xuICAgICAgICAgICAgQ29udGFjdExpc3QucmVtb3ZlQ29udGFjdChqaWQpO1xuICAgICAgICAgICAgVmlkZW9MYXlvdXQucmVtb3ZlQ29ubmVjdGlvbkluZGljYXRvcihqaWQpO1xuICAgICAgICAgICAgLy8gaGlkZSBoZXJlLCB3YWl0IGZvciB2aWRlbyB0byBjbG9zZSBiZWZvcmUgcmVtb3ZpbmdcbiAgICAgICAgICAgICQoY29udGFpbmVyKS5oaWRlKCk7XG4gICAgICAgICAgICBWaWRlb0xheW91dC5yZXNpemVUaHVtYm5haWxzKCk7XG4gICAgICAgIH1cbiAgICB9LCAxMCk7XG5cbiAgICAvLyBVbmxvY2sgbGFyZ2UgdmlkZW9cbiAgICBpZiAoZm9jdXNlZFZpZGVvSW5mbyAmJiBmb2N1c2VkVmlkZW9JbmZvLmppZCA9PT0gamlkKVxuICAgIHtcbiAgICAgICAgY29uc29sZS5pbmZvKFwiRm9jdXNlZCB2aWRlbyBvd25lciBoYXMgbGVmdCB0aGUgY29uZmVyZW5jZVwiKTtcbiAgICAgICAgZm9jdXNlZFZpZGVvSW5mbyA9IG51bGw7XG4gICAgfVxuXG59O1xuXG5VSS5nZXRTZXR0aW5ncyA9IGZ1bmN0aW9uICgpIHtcbiAgICByZXR1cm4gU2V0dGluZ3MuZ2V0U2V0dGluZ3MoKTtcbn07XG5cblVJLnRvZ2dsZUZpbG1TdHJpcCA9IGZ1bmN0aW9uICgpIHtcbiAgICByZXR1cm4gQm90dG9tVG9vbGJhci50b2dnbGVGaWxtU3RyaXAoKTtcbn07XG5cblVJLnRvZ2dsZUNoYXQgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIEJvdHRvbVRvb2xiYXIudG9nZ2xlQ2hhdCgpO1xufTtcblxuVUkudG9nZ2xlQ29udGFjdExpc3QgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIEJvdHRvbVRvb2xiYXIudG9nZ2xlQ29udGFjdExpc3QoKTtcbn07XG5cblVJLm9uTG9jYWxSb2xlQ2hhbmdlID0gZnVuY3Rpb24gKGppZCwgaW5mbywgcHJlcykge1xuXG4gICAgY29uc29sZS5pbmZvKFwiTXkgcm9sZSBjaGFuZ2VkLCBuZXcgcm9sZTogXCIgKyBpbmZvLnJvbGUpO1xuICAgIHZhciBpc01vZGVyYXRvciA9IHhtcHAuaXNNb2RlcmF0b3IoKTtcblxuICAgIFZpZGVvTGF5b3V0LnNob3dNb2RlcmF0b3JJbmRpY2F0b3IoKTtcbiAgICBUb29sYmFyLnNob3dBdXRoZW50aWNhdGVCdXR0b24oXG4gICAgICAgICAgICB4bXBwLmlzRXh0ZXJuYWxBdXRoRW5hYmxlZCgpICYmICFpc01vZGVyYXRvcik7XG5cbiAgICBpZiAoaXNNb2RlcmF0b3IpIHtcbiAgICAgICAgQXV0aGVudGljYXRpb24uY2xvc2VBdXRoZW50aWNhdGlvbldpbmRvdygpO1xuICAgICAgICBtZXNzYWdlSGFuZGxlci5ub3RpZnkoXG4gICAgICAgICAgICAnTWUnLCAnY29ubmVjdGVkJywgJ01vZGVyYXRvciByaWdodHMgZ3JhbnRlZCAhJyk7XG4gICAgfVxufTtcblxuVUkub25Nb2RlcmF0b3JTdGF0dXNDaGFuZ2VkID0gZnVuY3Rpb24gKGlzTW9kZXJhdG9yKSB7XG5cbiAgICBUb29sYmFyLnNob3dTaXBDYWxsQnV0dG9uKGlzTW9kZXJhdG9yKTtcbiAgICBUb29sYmFyLnNob3dSZWNvcmRpbmdCdXR0b24oXG4gICAgICAgIGlzTW9kZXJhdG9yKTsgLy8mJlxuICAgIC8vIEZJWE1FOlxuICAgIC8vIFJlY29yZGluZyB2aXNpYmxlIGlmXG4gICAgLy8gdGhlcmUgYXJlIGF0IGxlYXN0IDIoKyAxIGZvY3VzKSBwYXJ0aWNpcGFudHNcbiAgICAvL09iamVjdC5rZXlzKGNvbm5lY3Rpb24uZW11Yy5tZW1iZXJzKS5sZW5ndGggPj0gMyk7XG5cbiAgICBpZiAoaXNNb2RlcmF0b3IgJiYgY29uZmlnLmV0aGVycGFkX2Jhc2UpIHtcbiAgICAgICAgRXRoZXJwYWQuaW5pdCgpO1xuICAgIH1cbn07XG5cblVJLm9uUGFzc3dvcmRSZXFpdXJlZCA9IGZ1bmN0aW9uIChjYWxsYmFjaykge1xuICAgIC8vIHBhc3N3b3JkIGlzIHJlcXVpcmVkXG4gICAgVG9vbGJhci5sb2NrTG9ja0J1dHRvbigpO1xuXG4gICAgbWVzc2FnZUhhbmRsZXIub3BlblR3b0J1dHRvbkRpYWxvZyhudWxsLFxuICAgICAgICAgICAgJzxoMj5QYXNzd29yZCByZXF1aXJlZDwvaDI+JyArXG4gICAgICAgICAgICAnPGlucHV0IGlkPVwibG9ja0tleVwiIHR5cGU9XCJ0ZXh0XCIgcGxhY2Vob2xkZXI9XCJwYXNzd29yZFwiIGF1dG9mb2N1cz4nLFxuICAgICAgICB0cnVlLFxuICAgICAgICBcIk9rXCIsXG4gICAgICAgIGZ1bmN0aW9uIChlLCB2LCBtLCBmKSB7fSxcbiAgICAgICAgZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9ja0tleScpLmZvY3VzKCk7XG4gICAgICAgIH0sXG4gICAgICAgIGZ1bmN0aW9uIChlLCB2LCBtLCBmKSB7XG4gICAgICAgICAgICBpZiAodikge1xuICAgICAgICAgICAgICAgIHZhciBsb2NrS2V5ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvY2tLZXknKTtcbiAgICAgICAgICAgICAgICBpZiAobG9ja0tleS52YWx1ZSAhPT0gbnVsbCkge1xuICAgICAgICAgICAgICAgICAgICBUb29sYmFyLnNldFNoYXJlZEtleShsb2NrS2V5LnZhbHVlKTtcbiAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2sobG9ja0tleS52YWx1ZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgKTtcbn07XG5cblVJLm9uQXV0aGVudGljYXRpb25SZXF1aXJlZCA9IGZ1bmN0aW9uIChpbnRlcnZhbENhbGxiYWNrKSB7XG4gICAgQXV0aGVudGljYXRpb24ub3BlbkF1dGhlbnRpY2F0aW9uRGlhbG9nKFxuICAgICAgICByb29tTmFtZSwgaW50ZXJ2YWxDYWxsYmFjaywgZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgVG9vbGJhci5hdXRoZW50aWNhdGVDbGlja2VkKCk7XG4gICAgICAgIH0pO1xufTtcblxuVUkuc2V0UmVjb3JkaW5nQnV0dG9uU3RhdGUgPSBmdW5jdGlvbiAoc3RhdGUpIHtcbiAgICBUb29sYmFyLnNldFJlY29yZGluZ0J1dHRvblN0YXRlKHN0YXRlKTtcbn07XG5cblVJLmlucHV0RGlzcGxheU5hbWVIYW5kbGVyID0gZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgVmlkZW9MYXlvdXQuaW5wdXREaXNwbGF5TmFtZUhhbmRsZXIodmFsdWUpO1xufTtcblxuVUkub25NdWNFbnRlcmVkID0gZnVuY3Rpb24gKGppZCwgaWQsIGRpc3BsYXlOYW1lKSB7XG4gICAgbWVzc2FnZUhhbmRsZXIubm90aWZ5KGRpc3BsYXlOYW1lIHx8ICdTb21lYm9keScsXG4gICAgICAgICdjb25uZWN0ZWQnLFxuICAgICAgICAnY29ubmVjdGVkJyk7XG5cbiAgICAvLyBBZGQgUGVlcidzIGNvbnRhaW5lclxuICAgIFZpZGVvTGF5b3V0LmVuc3VyZVBlZXJDb250YWluZXJFeGlzdHMoamlkLGlkKTtcbn07XG5cblVJLm9uTXVjUHJlc2VuY2VTdGF0dXMgPSBmdW5jdGlvbiAoIGppZCwgaW5mbykge1xuICAgIFZpZGVvTGF5b3V0LnNldFByZXNlbmNlU3RhdHVzKFxuICAgICAgICAgICAgJ3BhcnRpY2lwYW50XycgKyBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChqaWQpLCBpbmZvLnN0YXR1cyk7XG59O1xuXG5VSS5vbk11Y1JvbGVDaGFuZ2VkID0gZnVuY3Rpb24gKHJvbGUsIGRpc3BsYXlOYW1lKSB7XG4gICAgVmlkZW9MYXlvdXQuc2hvd01vZGVyYXRvckluZGljYXRvcigpO1xuXG4gICAgaWYgKHJvbGUgPT09ICdtb2RlcmF0b3InKSB7XG4gICAgICAgIHZhciBkaXNwbGF5TmFtZSA9IGRpc3BsYXlOYW1lO1xuICAgICAgICBpZiAoIWRpc3BsYXlOYW1lKSB7XG4gICAgICAgICAgICBkaXNwbGF5TmFtZSA9ICdTb21lYm9keSc7XG4gICAgICAgIH1cbiAgICAgICAgbWVzc2FnZUhhbmRsZXIubm90aWZ5KFxuICAgICAgICAgICAgZGlzcGxheU5hbWUsXG4gICAgICAgICAgICAnY29ubmVjdGVkJyxcbiAgICAgICAgICAgICAgICAnTW9kZXJhdG9yIHJpZ2h0cyBncmFudGVkIHRvICcgKyBkaXNwbGF5TmFtZSArICchJyk7XG4gICAgfVxufTtcblxuVUkudXBkYXRlTG9jYWxDb25uZWN0aW9uU3RhdHMgPSBmdW5jdGlvbihwZXJjZW50LCBzdGF0cylcbntcbiAgICBWaWRlb0xheW91dC51cGRhdGVMb2NhbENvbm5lY3Rpb25TdGF0cyhwZXJjZW50LCBzdGF0cyk7XG59O1xuXG5VSS51cGRhdGVDb25uZWN0aW9uU3RhdHMgPSBmdW5jdGlvbihqaWQsIHBlcmNlbnQsIHN0YXRzKVxue1xuICAgIFZpZGVvTGF5b3V0LnVwZGF0ZUNvbm5lY3Rpb25TdGF0cyhqaWQsIHBlcmNlbnQsIHN0YXRzKTtcbn07XG5cblVJLm9uU3RhdHNTdG9wID0gZnVuY3Rpb24gKCkge1xuICAgIFZpZGVvTGF5b3V0Lm9uU3RhdHNTdG9wKCk7XG59O1xuXG5VSS5nZXRMYXJnZVZpZGVvU3RhdGUgPSBmdW5jdGlvbigpXG57XG4gICAgcmV0dXJuIFZpZGVvTGF5b3V0LmdldExhcmdlVmlkZW9TdGF0ZSgpO1xufTtcblxuVUkuc2hvd0xvY2FsQXVkaW9JbmRpY2F0b3IgPSBmdW5jdGlvbiAobXV0ZSkge1xuICAgIFZpZGVvTGF5b3V0LnNob3dMb2NhbEF1ZGlvSW5kaWNhdG9yKG11dGUpO1xufTtcblxuVUkuZ2VuZXJhdGVSb29tTmFtZSA9IGZ1bmN0aW9uKCkge1xuICAgIGlmKHJvb21OYW1lKVxuICAgICAgICByZXR1cm4gcm9vbU5hbWU7XG4gICAgdmFyIHJvb21ub2RlID0gbnVsbDtcbiAgICB2YXIgcGF0aCA9IHdpbmRvdy5sb2NhdGlvbi5wYXRobmFtZTtcblxuICAgIC8vIGRldGVybWluZGUgdGhlIHJvb20gbm9kZSBmcm9tIHRoZSB1cmxcbiAgICAvLyBUT0RPOiBqdXN0IHRoZSByb29tbm9kZSBvciB0aGUgd2hvbGUgYmFyZSBqaWQ/XG4gICAgaWYgKGNvbmZpZy5nZXRyb29tbm9kZSAmJiB0eXBlb2YgY29uZmlnLmdldHJvb21ub2RlID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIC8vIGN1c3RvbSBmdW5jdGlvbiBtaWdodCBiZSByZXNwb25zaWJsZSBmb3IgZG9pbmcgdGhlIHB1c2hzdGF0ZVxuICAgICAgICByb29tbm9kZSA9IGNvbmZpZy5nZXRyb29tbm9kZShwYXRoKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICAvKiBmYWxsIGJhY2sgdG8gZGVmYXVsdCBzdHJhdGVneVxuICAgICAgICAgKiB0aGlzIGlzIG1ha2luZyBhc3N1bXB0aW9ucyBhYm91dCBob3cgdGhlIFVSTC0+cm9vbSBtYXBwaW5nIGhhcHBlbnMuXG4gICAgICAgICAqIEl0IGN1cnJlbnRseSBhc3N1bWVzIGRlcGxveW1lbnQgYXQgcm9vdCwgd2l0aCBhIHJld3JpdGUgbGlrZSB0aGVcbiAgICAgICAgICogZm9sbG93aW5nIG9uZSAoZm9yIG5naW54KTpcbiAgICAgICAgIGxvY2F0aW9uIH4gXi8oW2EtekEtWjAtOV0rKSQge1xuICAgICAgICAgcmV3cml0ZSBeLyguKikkIC8gYnJlYWs7XG4gICAgICAgICB9XG4gICAgICAgICAqL1xuICAgICAgICBpZiAocGF0aC5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgICByb29tbm9kZSA9IHBhdGguc3Vic3RyKDEpLnRvTG93ZXJDYXNlKCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB2YXIgd29yZCA9IFJvb21OYW1lR2VuZXJhdG9yLmdlbmVyYXRlUm9vbVdpdGhvdXRTZXBhcmF0b3IoKTtcbiAgICAgICAgICAgIHJvb21ub2RlID0gd29yZC50b0xvd2VyQ2FzZSgpO1xuXG4gICAgICAgICAgICB3aW5kb3cuaGlzdG9yeS5wdXNoU3RhdGUoJ1ZpZGVvQ2hhdCcsXG4gICAgICAgICAgICAgICAgICAgICdSb29tOiAnICsgd29yZCwgd2luZG93LmxvY2F0aW9uLnBhdGhuYW1lICsgd29yZCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICByb29tTmFtZSA9IHJvb21ub2RlICsgJ0AnICsgY29uZmlnLmhvc3RzLm11YztcbiAgICByZXR1cm4gcm9vbU5hbWU7XG59O1xuXG5cblVJLmNvbm5lY3Rpb25JbmRpY2F0b3JTaG93TW9yZSA9IGZ1bmN0aW9uKGlkKVxue1xuICAgIHJldHVybiBWaWRlb0xheW91dC5jb25uZWN0aW9uSW5kaWNhdG9yc1tpZF0uc2hvd01vcmUoKTtcbn07XG5cblVJLnNob3dUb29sYmFyID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiBUb29sYmFyVG9nZ2xlci5zaG93VG9vbGJhcigpO1xufTtcblxuVUkuZG9ja1Rvb2xiYXIgPSBmdW5jdGlvbiAoaXNEb2NrKSB7XG4gICAgcmV0dXJuIFRvb2xiYXJUb2dnbGVyLmRvY2tUb29sYmFyKGlzRG9jayk7XG59O1xuXG5VSS5nZXRDcmVhZGVudGlhbHMgPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgYm9zaDogZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2Jvc2hVUkwnKS52YWx1ZSxcbiAgICAgICAgcGFzc3dvcmQ6IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdwYXNzd29yZCcpLnZhbHVlLFxuICAgICAgICBqaWQ6IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdqaWQnKS52YWx1ZVxuICAgIH07XG59O1xuXG5VSS5kaXNhYmxlQ29ubmVjdCA9IGZ1bmN0aW9uICgpIHtcbiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnY29ubmVjdCcpLmRpc2FibGVkID0gdHJ1ZTtcbn07XG5cblVJLnNob3dMb2dpblBvcHVwID0gZnVuY3Rpb24oY2FsbGJhY2spXG57XG4gICAgY29uc29sZS5sb2coJ3Bhc3N3b3JkIGlzIHJlcXVpcmVkJyk7XG5cbiAgICBVSS5tZXNzYWdlSGFuZGxlci5vcGVuVHdvQnV0dG9uRGlhbG9nKG51bGwsXG4gICAgICAgICAgICAnPGgyPlBhc3N3b3JkIHJlcXVpcmVkPC9oMj4nICtcbiAgICAgICAgICAgICc8aW5wdXQgaWQ9XCJwYXNzd29yZHJlcXVpcmVkLnVzZXJuYW1lXCIgdHlwZT1cInRleHRcIiBwbGFjZWhvbGRlcj1cInVzZXJAZG9tYWluLm5ldFwiIGF1dG9mb2N1cz4nICtcbiAgICAgICAgICAgICc8aW5wdXQgaWQ9XCJwYXNzd29yZHJlcXVpcmVkLnBhc3N3b3JkXCIgdHlwZT1cInBhc3N3b3JkXCIgcGxhY2Vob2xkZXI9XCJ1c2VyIHBhc3N3b3JkXCI+JyxcbiAgICAgICAgdHJ1ZSxcbiAgICAgICAgXCJPa1wiLFxuICAgICAgICBmdW5jdGlvbiAoZSwgdiwgbSwgZikge1xuICAgICAgICAgICAgaWYgKHYpIHtcbiAgICAgICAgICAgICAgICB2YXIgdXNlcm5hbWUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgncGFzc3dvcmRyZXF1aXJlZC51c2VybmFtZScpO1xuICAgICAgICAgICAgICAgIHZhciBwYXNzd29yZCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdwYXNzd29yZHJlcXVpcmVkLnBhc3N3b3JkJyk7XG5cbiAgICAgICAgICAgICAgICBpZiAodXNlcm5hbWUudmFsdWUgIT09IG51bGwgJiYgcGFzc3dvcmQudmFsdWUgIT0gbnVsbCkge1xuICAgICAgICAgICAgICAgICAgICBjYWxsYmFjayh1c2VybmFtZS52YWx1ZSwgcGFzc3dvcmQudmFsdWUpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgncGFzc3dvcmRyZXF1aXJlZC51c2VybmFtZScpLmZvY3VzKCk7XG4gICAgICAgIH1cbiAgICApO1xufVxuXG5VSS5jaGVja0Zvck5pY2tuYW1lQW5kSm9pbiA9IGZ1bmN0aW9uICgpIHtcblxuICAgIEF1dGhlbnRpY2F0aW9uLmNsb3NlQXV0aGVudGljYXRpb25EaWFsb2coKTtcbiAgICBBdXRoZW50aWNhdGlvbi5zdG9wSW50ZXJ2YWwoKTtcblxuICAgIHZhciBuaWNrID0gbnVsbDtcbiAgICBpZiAoY29uZmlnLnVzZU5pY2tzKSB7XG4gICAgICAgIG5pY2sgPSB3aW5kb3cucHJvbXB0KCdZb3VyIG5pY2tuYW1lIChvcHRpb25hbCknKTtcbiAgICB9XG4gICAgeG1wcC5qb2luUm9vb20ocm9vbU5hbWUsIGNvbmZpZy51c2VOaWNrcywgbmljayk7XG59XG5cblxuZnVuY3Rpb24gZHVtcChlbGVtLCBmaWxlbmFtZSkge1xuICAgIGVsZW0gPSBlbGVtLnBhcmVudE5vZGU7XG4gICAgZWxlbS5kb3dubG9hZCA9IGZpbGVuYW1lIHx8ICdtZWV0bG9nLmpzb24nO1xuICAgIGVsZW0uaHJlZiA9ICdkYXRhOmFwcGxpY2F0aW9uL2pzb247Y2hhcnNldD11dGYtOCxcXG4nO1xuICAgIHZhciBkYXRhID0geG1wcC5wb3B1bGF0ZURhdGEoKTtcbiAgICB2YXIgbWV0YWRhdGEgPSB7fTtcbiAgICBtZXRhZGF0YS50aW1lID0gbmV3IERhdGUoKTtcbiAgICBtZXRhZGF0YS51cmwgPSB3aW5kb3cubG9jYXRpb24uaHJlZjtcbiAgICBtZXRhZGF0YS51YSA9IG5hdmlnYXRvci51c2VyQWdlbnQ7XG4gICAgdmFyIGxvZyA9IHhtcHAuZ2V0TG9nZ2VyKCk7XG4gICAgaWYgKGxvZykge1xuICAgICAgICBtZXRhZGF0YS54bXBwID0gbG9nO1xuICAgIH1cbiAgICBkYXRhLm1ldGFkYXRhID0gbWV0YWRhdGE7XG4gICAgZWxlbS5ocmVmICs9IGVuY29kZVVSSUNvbXBvbmVudChKU09OLnN0cmluZ2lmeShkYXRhLCBudWxsLCAnICAnKSk7XG4gICAgcmV0dXJuIGZhbHNlO1xufVxuXG5VSS5nZXRSb29tTmFtZSA9IGZ1bmN0aW9uICgpIHtcbiAgICByZXR1cm4gcm9vbU5hbWU7XG59XG5cbi8qKlxuICogTXV0ZXMvdW5tdXRlcyB0aGUgbG9jYWwgdmlkZW8uXG4gKlxuICogQHBhcmFtIG11dGUgPHR0PnRydWU8L3R0PiB0byBtdXRlIHRoZSBsb2NhbCB2aWRlbzsgb3RoZXJ3aXNlLCA8dHQ+ZmFsc2U8L3R0PlxuICogQHBhcmFtIG9wdGlvbnMgYW4gb2JqZWN0IHdoaWNoIHNwZWNpZmllcyBvcHRpb25hbCBhcmd1bWVudHMgc3VjaCBhcyB0aGVcbiAqIDx0dD5ib29sZWFuPC90dD4ga2V5IDx0dD5ieVVzZXI8L3R0PiB3aXRoIGRlZmF1bHQgdmFsdWUgPHR0PnRydWU8L3R0PiB3aGljaFxuICogc3BlY2lmaWVzIHdoZXRoZXIgdGhlIG1ldGhvZCB3YXMgaW5pdGlhdGVkIGluIHJlc3BvbnNlIHRvIGEgdXNlciBjb21tYW5kIChpblxuICogY29udHJhc3QgdG8gYW4gYXV0b21hdGljIGRlY2lzaW9uIHRha2VuIGJ5IHRoZSBhcHBsaWNhdGlvbiBsb2dpYylcbiAqL1xuZnVuY3Rpb24gc2V0VmlkZW9NdXRlKG11dGUsIG9wdGlvbnMpIHtcbiAgICB4bXBwLnNldFZpZGVvTXV0ZShcbiAgICAgICAgbXV0ZSxcbiAgICAgICAgZnVuY3Rpb24gKG11dGUpIHtcbiAgICAgICAgICAgIHZhciB2aWRlbyA9ICQoJyN2aWRlbycpO1xuICAgICAgICAgICAgdmFyIGNvbW11bmljYXRpdmVDbGFzcyA9IFwiaWNvbi1jYW1lcmFcIjtcbiAgICAgICAgICAgIHZhciBtdXRlQ2xhc3MgPSBcImljb24tY2FtZXJhIGljb24tY2FtZXJhLWRpc2FibGVkXCI7XG5cbiAgICAgICAgICAgIGlmIChtdXRlKSB7XG4gICAgICAgICAgICAgICAgdmlkZW8ucmVtb3ZlQ2xhc3MoY29tbXVuaWNhdGl2ZUNsYXNzKTtcbiAgICAgICAgICAgICAgICB2aWRlby5hZGRDbGFzcyhtdXRlQ2xhc3MpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICB2aWRlby5yZW1vdmVDbGFzcyhtdXRlQ2xhc3MpO1xuICAgICAgICAgICAgICAgIHZpZGVvLmFkZENsYXNzKGNvbW11bmljYXRpdmVDbGFzcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIE11dGVzL3VubXV0ZXMgdGhlIGxvY2FsIHZpZGVvLlxuICovXG5VSS50b2dnbGVWaWRlbyA9IGZ1bmN0aW9uICgpIHtcbiAgICBVSVV0aWwuYnV0dG9uQ2xpY2soXCIjdmlkZW9cIiwgXCJpY29uLWNhbWVyYSBpY29uLWNhbWVyYS1kaXNhYmxlZFwiKTtcblxuICAgIHNldFZpZGVvTXV0ZSghUlRDLmxvY2FsVmlkZW8uaXNNdXRlZCgpKTtcbn07XG5cbi8qKlxuICogTXV0ZXMgLyB1bm11dGVzIGF1ZGlvIGZvciB0aGUgbG9jYWwgcGFydGljaXBhbnQuXG4gKi9cblVJLnRvZ2dsZUF1ZGlvID0gZnVuY3Rpb24oKSB7XG4gICAgVUkuc2V0QXVkaW9NdXRlZCghUlRDLmxvY2FsQXVkaW8uaXNNdXRlZCgpKTtcbn07XG5cbi8qKlxuICogU2V0cyBtdXRlZCBhdWRpbyBzdGF0ZSBmb3IgdGhlIGxvY2FsIHBhcnRpY2lwYW50LlxuICovXG5VSS5zZXRBdWRpb011dGVkID0gZnVuY3Rpb24gKG11dGUpIHtcblxuICAgIGlmKCF4bXBwLnNldEF1ZGlvTXV0ZShtdXRlLCBmdW5jdGlvbiAoKSB7XG4gICAgICAgIFVJLnNob3dMb2NhbEF1ZGlvSW5kaWNhdG9yKG11dGUpO1xuXG4gICAgICAgIFVJVXRpbC5idXR0b25DbGljayhcIiNtdXRlXCIsIFwiaWNvbi1taWNyb3Bob25lIGljb24tbWljLWRpc2FibGVkXCIpO1xuICAgIH0pKVxuICAgIHtcbiAgICAgICAgLy8gV2Ugc3RpbGwgY2xpY2sgdGhlIGJ1dHRvbi5cbiAgICAgICAgVUlVdGlsLmJ1dHRvbkNsaWNrKFwiI211dGVcIiwgXCJpY29uLW1pY3JvcGhvbmUgaWNvbi1taWMtZGlzYWJsZWRcIik7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbn1cblxuVUkub25MYXN0TkNoYW5nZWQgPSBmdW5jdGlvbiAob2xkVmFsdWUsIG5ld1ZhbHVlKSB7XG4gICAgaWYgKGNvbmZpZy5tdXRlTG9jYWxWaWRlb0lmTm90SW5MYXN0Tikge1xuICAgICAgICBzZXRWaWRlb011dGUoIW5ld1ZhbHVlLCB7ICdieVVzZXInOiBmYWxzZSB9KTtcbiAgICB9XG59XG5cbm1vZHVsZS5leHBvcnRzID0gVUk7XG5cbiIsInZhciBDYW52YXNVdGlsID0gcmVxdWlyZShcIi4vQ2FudmFzVXRpbHNcIik7XG5cbi8qKlxuICogVGhlIGF1ZGlvIExldmVscyBwbHVnaW4uXG4gKi9cbnZhciBBdWRpb0xldmVscyA9IChmdW5jdGlvbihteSkge1xuICAgIHZhciBhdWRpb0xldmVsQ2FudmFzQ2FjaGUgPSB7fTtcblxuICAgIG15LkxPQ0FMX0xFVkVMID0gJ2xvY2FsJztcblxuICAgIC8qKlxuICAgICAqIFVwZGF0ZXMgdGhlIGF1ZGlvIGxldmVsIGNhbnZhcyBmb3IgdGhlIGdpdmVuIHBlZXJKaWQuIElmIHRoZSBjYW52YXNcbiAgICAgKiBkaWRuJ3QgZXhpc3Qgd2UgY3JlYXRlIGl0LlxuICAgICAqL1xuICAgIG15LnVwZGF0ZUF1ZGlvTGV2ZWxDYW52YXMgPSBmdW5jdGlvbiAocGVlckppZCwgVmlkZW9MYXlvdXQpIHtcbiAgICAgICAgdmFyIHJlc291cmNlSmlkID0gbnVsbDtcbiAgICAgICAgdmFyIHZpZGVvU3BhbklkID0gbnVsbDtcbiAgICAgICAgaWYgKCFwZWVySmlkKVxuICAgICAgICAgICAgdmlkZW9TcGFuSWQgPSAnbG9jYWxWaWRlb0NvbnRhaW5lcic7XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgcmVzb3VyY2VKaWQgPSBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChwZWVySmlkKTtcblxuICAgICAgICAgICAgdmlkZW9TcGFuSWQgPSAncGFydGljaXBhbnRfJyArIHJlc291cmNlSmlkO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIHZpZGVvU3BhbiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHZpZGVvU3BhbklkKTtcblxuICAgICAgICBpZiAoIXZpZGVvU3Bhbikge1xuICAgICAgICAgICAgaWYgKHJlc291cmNlSmlkKVxuICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJObyB2aWRlbyBlbGVtZW50IGZvciBqaWRcIiwgcmVzb3VyY2VKaWQpO1xuICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJObyB2aWRlbyBlbGVtZW50IGZvciBsb2NhbCB2aWRlby5cIik7XG5cbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBhdWRpb0xldmVsQ2FudmFzID0gJCgnIycgKyB2aWRlb1NwYW5JZCArICc+Y2FudmFzJyk7XG5cbiAgICAgICAgdmFyIHZpZGVvU3BhY2VXaWR0aCA9ICQoJyNyZW1vdGVWaWRlb3MnKS53aWR0aCgpO1xuICAgICAgICB2YXIgdGh1bWJuYWlsU2l6ZSA9IFZpZGVvTGF5b3V0LmNhbGN1bGF0ZVRodW1ibmFpbFNpemUodmlkZW9TcGFjZVdpZHRoKTtcbiAgICAgICAgdmFyIHRodW1ibmFpbFdpZHRoID0gdGh1bWJuYWlsU2l6ZVswXTtcbiAgICAgICAgdmFyIHRodW1ibmFpbEhlaWdodCA9IHRodW1ibmFpbFNpemVbMV07XG5cbiAgICAgICAgaWYgKCFhdWRpb0xldmVsQ2FudmFzIHx8IGF1ZGlvTGV2ZWxDYW52YXMubGVuZ3RoID09PSAwKSB7XG5cbiAgICAgICAgICAgIGF1ZGlvTGV2ZWxDYW52YXMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdjYW52YXMnKTtcbiAgICAgICAgICAgIGF1ZGlvTGV2ZWxDYW52YXMuY2xhc3NOYW1lID0gXCJhdWRpb2xldmVsXCI7XG4gICAgICAgICAgICBhdWRpb0xldmVsQ2FudmFzLnN0eWxlLmJvdHRvbSA9IFwiLVwiICsgaW50ZXJmYWNlQ29uZmlnLkNBTlZBU19FWFRSQS8yICsgXCJweFwiO1xuICAgICAgICAgICAgYXVkaW9MZXZlbENhbnZhcy5zdHlsZS5sZWZ0ID0gXCItXCIgKyBpbnRlcmZhY2VDb25maWcuQ0FOVkFTX0VYVFJBLzIgKyBcInB4XCI7XG4gICAgICAgICAgICByZXNpemVBdWRpb0xldmVsQ2FudmFzKCBhdWRpb0xldmVsQ2FudmFzLFxuICAgICAgICAgICAgICAgICAgICB0aHVtYm5haWxXaWR0aCxcbiAgICAgICAgICAgICAgICAgICAgdGh1bWJuYWlsSGVpZ2h0KTtcblxuICAgICAgICAgICAgdmlkZW9TcGFuLmFwcGVuZENoaWxkKGF1ZGlvTGV2ZWxDYW52YXMpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgYXVkaW9MZXZlbENhbnZhcyA9IGF1ZGlvTGV2ZWxDYW52YXMuZ2V0KDApO1xuXG4gICAgICAgICAgICByZXNpemVBdWRpb0xldmVsQ2FudmFzKCBhdWRpb0xldmVsQ2FudmFzLFxuICAgICAgICAgICAgICAgICAgICB0aHVtYm5haWxXaWR0aCxcbiAgICAgICAgICAgICAgICAgICAgdGh1bWJuYWlsSGVpZ2h0KTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBVcGRhdGVzIHRoZSBhdWRpbyBsZXZlbCBVSSBmb3IgdGhlIGdpdmVuIHJlc291cmNlSmlkLlxuICAgICAqXG4gICAgICogQHBhcmFtIHJlc291cmNlSmlkIHRoZSByZXNvdXJjZSBqaWQgaW5kaWNhdGluZyB0aGUgdmlkZW8gZWxlbWVudCBmb3JcbiAgICAgKiB3aGljaCB3ZSBkcmF3IHRoZSBhdWRpbyBsZXZlbFxuICAgICAqIEBwYXJhbSBhdWRpb0xldmVsIHRoZSBuZXdBdWRpbyBsZXZlbCB0byByZW5kZXJcbiAgICAgKi9cbiAgICBteS51cGRhdGVBdWRpb0xldmVsID0gZnVuY3Rpb24gKHJlc291cmNlSmlkLCBhdWRpb0xldmVsLCBsYXJnZVZpZGVvUmVzb3VyY2VKaWQpIHtcbiAgICAgICAgZHJhd0F1ZGlvTGV2ZWxDYW52YXMocmVzb3VyY2VKaWQsIGF1ZGlvTGV2ZWwpO1xuXG4gICAgICAgIHZhciB2aWRlb1NwYW5JZCA9IGdldFZpZGVvU3BhbklkKHJlc291cmNlSmlkKTtcblxuICAgICAgICB2YXIgYXVkaW9MZXZlbENhbnZhcyA9ICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnPmNhbnZhcycpLmdldCgwKTtcblxuICAgICAgICBpZiAoIWF1ZGlvTGV2ZWxDYW52YXMpXG4gICAgICAgICAgICByZXR1cm47XG5cbiAgICAgICAgdmFyIGRyYXdDb250ZXh0ID0gYXVkaW9MZXZlbENhbnZhcy5nZXRDb250ZXh0KCcyZCcpO1xuXG4gICAgICAgIHZhciBjYW52YXNDYWNoZSA9IGF1ZGlvTGV2ZWxDYW52YXNDYWNoZVtyZXNvdXJjZUppZF07XG5cbiAgICAgICAgZHJhd0NvbnRleHQuY2xlYXJSZWN0ICgwLCAwLFxuICAgICAgICAgICAgICAgIGF1ZGlvTGV2ZWxDYW52YXMud2lkdGgsIGF1ZGlvTGV2ZWxDYW52YXMuaGVpZ2h0KTtcbiAgICAgICAgZHJhd0NvbnRleHQuZHJhd0ltYWdlKGNhbnZhc0NhY2hlLCAwLCAwKTtcblxuICAgICAgICBpZihyZXNvdXJjZUppZCA9PT0gQXVkaW9MZXZlbHMuTE9DQUxfTEVWRUwpIHtcbiAgICAgICAgICAgIGlmKCF4bXBwLm15SmlkKCkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXNvdXJjZUppZCA9IHhtcHAubXlSZXNvdXJjZSgpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYocmVzb3VyY2VKaWQgID09PSBsYXJnZVZpZGVvUmVzb3VyY2VKaWQpIHtcbiAgICAgICAgICAgIEF1ZGlvTGV2ZWxzLnVwZGF0ZUFjdGl2ZVNwZWFrZXJBdWRpb0xldmVsKGF1ZGlvTGV2ZWwpO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIG15LnVwZGF0ZUFjdGl2ZVNwZWFrZXJBdWRpb0xldmVsID0gZnVuY3Rpb24oYXVkaW9MZXZlbCkge1xuICAgICAgICB2YXIgZHJhd0NvbnRleHQgPSAkKCcjYWN0aXZlU3BlYWtlckF1ZGlvTGV2ZWwnKVswXS5nZXRDb250ZXh0KCcyZCcpO1xuICAgICAgICB2YXIgciA9IGludGVyZmFjZUNvbmZpZy5BQ1RJVkVfU1BFQUtFUl9BVkFUQVJfU0laRSAvIDI7XG4gICAgICAgIHZhciBjZW50ZXIgPSAoaW50ZXJmYWNlQ29uZmlnLkFDVElWRV9TUEVBS0VSX0FWQVRBUl9TSVpFICsgcikgLyAyO1xuXG4gICAgICAgIC8vIFNhdmUgdGhlIHByZXZpb3VzIHN0YXRlIG9mIHRoZSBjb250ZXh0LlxuICAgICAgICBkcmF3Q29udGV4dC5zYXZlKCk7XG5cbiAgICAgICAgZHJhd0NvbnRleHQuY2xlYXJSZWN0KDAsIDAsIDMwMCwgMzAwKTtcblxuICAgICAgICAvLyBEcmF3IGEgY2lyY2xlLlxuICAgICAgICBkcmF3Q29udGV4dC5hcmMoY2VudGVyLCBjZW50ZXIsIHIsIDAsIDIgKiBNYXRoLlBJKTtcblxuICAgICAgICAvLyBBZGQgYSBzaGFkb3cgYXJvdW5kIHRoZSBjaXJjbGVcbiAgICAgICAgZHJhd0NvbnRleHQuc2hhZG93Q29sb3IgPSBpbnRlcmZhY2VDb25maWcuU0hBRE9XX0NPTE9SO1xuICAgICAgICBkcmF3Q29udGV4dC5zaGFkb3dCbHVyID0gZ2V0U2hhZG93TGV2ZWwoYXVkaW9MZXZlbCk7XG4gICAgICAgIGRyYXdDb250ZXh0LnNoYWRvd09mZnNldFggPSAwO1xuICAgICAgICBkcmF3Q29udGV4dC5zaGFkb3dPZmZzZXRZID0gMDtcblxuICAgICAgICAvLyBGaWxsIHRoZSBzaGFwZS5cbiAgICAgICAgZHJhd0NvbnRleHQuZmlsbCgpO1xuXG4gICAgICAgIGRyYXdDb250ZXh0LnNhdmUoKTtcblxuICAgICAgICBkcmF3Q29udGV4dC5yZXN0b3JlKCk7XG5cblxuICAgICAgICBkcmF3Q29udGV4dC5hcmMoY2VudGVyLCBjZW50ZXIsIHIsIDAsIDIgKiBNYXRoLlBJKTtcblxuICAgICAgICBkcmF3Q29udGV4dC5jbGlwKCk7XG4gICAgICAgIGRyYXdDb250ZXh0LmNsZWFyUmVjdCgwLCAwLCAyNzcsIDIwMCk7XG5cbiAgICAgICAgLy8gUmVzdG9yZSB0aGUgcHJldmlvdXMgY29udGV4dCBzdGF0ZS5cbiAgICAgICAgZHJhd0NvbnRleHQucmVzdG9yZSgpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBSZXNpemVzIHRoZSBnaXZlbiBhdWRpbyBsZXZlbCBjYW52YXMgdG8gbWF0Y2ggdGhlIGdpdmVuIHRodW1ibmFpbCBzaXplLlxuICAgICAqL1xuICAgIGZ1bmN0aW9uIHJlc2l6ZUF1ZGlvTGV2ZWxDYW52YXMoYXVkaW9MZXZlbENhbnZhcyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRodW1ibmFpbFdpZHRoLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGh1bWJuYWlsSGVpZ2h0KSB7XG4gICAgICAgIGF1ZGlvTGV2ZWxDYW52YXMud2lkdGggPSB0aHVtYm5haWxXaWR0aCArIGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkE7XG4gICAgICAgIGF1ZGlvTGV2ZWxDYW52YXMuaGVpZ2h0ID0gdGh1bWJuYWlsSGVpZ2h0ICsgaW50ZXJmYWNlQ29uZmlnLkNBTlZBU19FWFRSQTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBEcmF3cyB0aGUgYXVkaW8gbGV2ZWwgY2FudmFzIGludG8gdGhlIGNhY2hlZCBjYW52YXMgb2JqZWN0LlxuICAgICAqXG4gICAgICogQHBhcmFtIHJlc291cmNlSmlkIHRoZSByZXNvdXJjZSBqaWQgaW5kaWNhdGluZyB0aGUgdmlkZW8gZWxlbWVudCBmb3JcbiAgICAgKiB3aGljaCB3ZSBkcmF3IHRoZSBhdWRpbyBsZXZlbFxuICAgICAqIEBwYXJhbSBhdWRpb0xldmVsIHRoZSBuZXdBdWRpbyBsZXZlbCB0byByZW5kZXJcbiAgICAgKi9cbiAgICBmdW5jdGlvbiBkcmF3QXVkaW9MZXZlbENhbnZhcyhyZXNvdXJjZUppZCwgYXVkaW9MZXZlbCkge1xuICAgICAgICBpZiAoIWF1ZGlvTGV2ZWxDYW52YXNDYWNoZVtyZXNvdXJjZUppZF0pIHtcblxuICAgICAgICAgICAgdmFyIHZpZGVvU3BhbklkID0gZ2V0VmlkZW9TcGFuSWQocmVzb3VyY2VKaWQpO1xuXG4gICAgICAgICAgICB2YXIgYXVkaW9MZXZlbENhbnZhc09yaWcgPSAkKCcjJyArIHZpZGVvU3BhbklkICsgJz5jYW52YXMnKS5nZXQoMCk7XG5cbiAgICAgICAgICAgIC8qXG4gICAgICAgICAgICAgKiBGSVhNRSBUZXN0aW5nIGhhcyBzaG93biB0aGF0IGF1ZGlvTGV2ZWxDYW52YXNPcmlnIG1heSBub3QgZXhpc3QuXG4gICAgICAgICAgICAgKiBJbiBzdWNoIGEgY2FzZSwgdGhlIG1ldGhvZCBDYW52YXNVdGlsLmNsb25lQ2FudmFzIG1heSB0aHJvdyBhblxuICAgICAgICAgICAgICogZXJyb3IuIFNpbmNlIGF1ZGlvIGxldmVscyBhcmUgZnJlcXVlbnRseSB1cGRhdGVkLCB0aGUgZXJyb3JzIGhhdmVcbiAgICAgICAgICAgICAqIGJlZW4gb2JzZXJ2ZWQgdG8gcGlsZSBpbnRvIHRoZSBjb25zb2xlLCBzdHJhaW4gdGhlIENQVS5cbiAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgaWYgKGF1ZGlvTGV2ZWxDYW52YXNPcmlnKVxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIGF1ZGlvTGV2ZWxDYW52YXNDYWNoZVtyZXNvdXJjZUppZF1cbiAgICAgICAgICAgICAgICAgICAgPSBDYW52YXNVdGlsLmNsb25lQ2FudmFzKGF1ZGlvTGV2ZWxDYW52YXNPcmlnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBjYW52YXMgPSBhdWRpb0xldmVsQ2FudmFzQ2FjaGVbcmVzb3VyY2VKaWRdO1xuXG4gICAgICAgIGlmICghY2FudmFzKVxuICAgICAgICAgICAgcmV0dXJuO1xuXG4gICAgICAgIHZhciBkcmF3Q29udGV4dCA9IGNhbnZhcy5nZXRDb250ZXh0KCcyZCcpO1xuXG4gICAgICAgIGRyYXdDb250ZXh0LmNsZWFyUmVjdCgwLCAwLCBjYW52YXMud2lkdGgsIGNhbnZhcy5oZWlnaHQpO1xuXG4gICAgICAgIHZhciBzaGFkb3dMZXZlbCA9IGdldFNoYWRvd0xldmVsKGF1ZGlvTGV2ZWwpO1xuXG4gICAgICAgIGlmIChzaGFkb3dMZXZlbCA+IDApXG4gICAgICAgICAgICAvLyBkcmF3Q29udGV4dCwgeCwgeSwgdywgaCwgciwgc2hhZG93Q29sb3IsIHNoYWRvd0xldmVsXG4gICAgICAgICAgICBDYW52YXNVdGlsLmRyYXdSb3VuZFJlY3RHbG93KCAgIGRyYXdDb250ZXh0LFxuICAgICAgICAgICAgICAgIGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkEvMiwgaW50ZXJmYWNlQ29uZmlnLkNBTlZBU19FWFRSQS8yLFxuICAgICAgICAgICAgICAgIGNhbnZhcy53aWR0aCAtIGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkEsXG4gICAgICAgICAgICAgICAgY2FudmFzLmhlaWdodCAtIGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkEsXG4gICAgICAgICAgICAgICAgaW50ZXJmYWNlQ29uZmlnLkNBTlZBU19SQURJVVMsXG4gICAgICAgICAgICAgICAgaW50ZXJmYWNlQ29uZmlnLlNIQURPV19DT0xPUixcbiAgICAgICAgICAgICAgICBzaGFkb3dMZXZlbCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgc2hhZG93L2dsb3cgbGV2ZWwgZm9yIHRoZSBnaXZlbiBhdWRpbyBsZXZlbC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBhdWRpb0xldmVsIHRoZSBhdWRpbyBsZXZlbCBmcm9tIHdoaWNoIHdlIGRldGVybWluZSB0aGUgc2hhZG93XG4gICAgICogbGV2ZWxcbiAgICAgKi9cbiAgICBmdW5jdGlvbiBnZXRTaGFkb3dMZXZlbCAoYXVkaW9MZXZlbCkge1xuICAgICAgICB2YXIgc2hhZG93TGV2ZWwgPSAwO1xuXG4gICAgICAgIGlmIChhdWRpb0xldmVsIDw9IDAuMykge1xuICAgICAgICAgICAgc2hhZG93TGV2ZWwgPSBNYXRoLnJvdW5kKGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkEvMiooYXVkaW9MZXZlbC8wLjMpKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChhdWRpb0xldmVsIDw9IDAuNikge1xuICAgICAgICAgICAgc2hhZG93TGV2ZWwgPSBNYXRoLnJvdW5kKGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkEvMiooKGF1ZGlvTGV2ZWwgLSAwLjMpIC8gMC4zKSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBzaGFkb3dMZXZlbCA9IE1hdGgucm91bmQoaW50ZXJmYWNlQ29uZmlnLkNBTlZBU19FWFRSQS8yKigoYXVkaW9MZXZlbCAtIDAuNikgLyAwLjQpKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc2hhZG93TGV2ZWw7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgdmlkZW8gc3BhbiBpZCBjb3JyZXNwb25kaW5nIHRvIHRoZSBnaXZlbiByZXNvdXJjZUppZCBvciBsb2NhbFxuICAgICAqIHVzZXIuXG4gICAgICovXG4gICAgZnVuY3Rpb24gZ2V0VmlkZW9TcGFuSWQocmVzb3VyY2VKaWQpIHtcbiAgICAgICAgdmFyIHZpZGVvU3BhbklkID0gbnVsbDtcbiAgICAgICAgaWYgKHJlc291cmNlSmlkID09PSBBdWRpb0xldmVscy5MT0NBTF9MRVZFTFxuICAgICAgICAgICAgICAgIHx8ICh4bXBwLm15UmVzb3VyY2UoKSAmJiByZXNvdXJjZUppZFxuICAgICAgICAgICAgICAgICAgICA9PT0geG1wcC5teVJlc291cmNlKCkpKVxuICAgICAgICAgICAgdmlkZW9TcGFuSWQgPSAnbG9jYWxWaWRlb0NvbnRhaW5lcic7XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIHZpZGVvU3BhbklkID0gJ3BhcnRpY2lwYW50XycgKyByZXNvdXJjZUppZDtcblxuICAgICAgICByZXR1cm4gdmlkZW9TcGFuSWQ7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogSW5kaWNhdGVzIHRoYXQgdGhlIHJlbW90ZSB2aWRlbyBoYXMgYmVlbiByZXNpemVkLlxuICAgICAqL1xuICAgICQoZG9jdW1lbnQpLmJpbmQoJ3JlbW90ZXZpZGVvLnJlc2l6ZWQnLCBmdW5jdGlvbiAoZXZlbnQsIHdpZHRoLCBoZWlnaHQpIHtcbiAgICAgICAgdmFyIHJlc2l6ZWQgPSBmYWxzZTtcbiAgICAgICAgJCgnI3JlbW90ZVZpZGVvcz5zcGFuPmNhbnZhcycpLmVhY2goZnVuY3Rpb24oKSB7XG4gICAgICAgICAgICB2YXIgY2FudmFzID0gJCh0aGlzKS5nZXQoMCk7XG4gICAgICAgICAgICBpZiAoY2FudmFzLndpZHRoICE9PSB3aWR0aCArIGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkEpIHtcbiAgICAgICAgICAgICAgICBjYW52YXMud2lkdGggPSB3aWR0aCArIGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkE7XG4gICAgICAgICAgICAgICAgcmVzaXplZCA9IHRydWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChjYW52YXMuaGVpZ2ggIT09IGhlaWdodCArIGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkEpIHtcbiAgICAgICAgICAgICAgICBjYW52YXMuaGVpZ2h0ID0gaGVpZ2h0ICsgaW50ZXJmYWNlQ29uZmlnLkNBTlZBU19FWFRSQTtcbiAgICAgICAgICAgICAgICByZXNpemVkID0gdHJ1ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICAgICAgaWYgKHJlc2l6ZWQpXG4gICAgICAgICAgICBPYmplY3Qua2V5cyhhdWRpb0xldmVsQ2FudmFzQ2FjaGUpLmZvckVhY2goZnVuY3Rpb24gKHJlc291cmNlSmlkKSB7XG4gICAgICAgICAgICAgICAgYXVkaW9MZXZlbENhbnZhc0NhY2hlW3Jlc291cmNlSmlkXS53aWR0aFxuICAgICAgICAgICAgICAgICAgICA9IHdpZHRoICsgaW50ZXJmYWNlQ29uZmlnLkNBTlZBU19FWFRSQTtcbiAgICAgICAgICAgICAgICBhdWRpb0xldmVsQ2FudmFzQ2FjaGVbcmVzb3VyY2VKaWRdLmhlaWdodFxuICAgICAgICAgICAgICAgICAgICA9IGhlaWdodCArIGludGVyZmFjZUNvbmZpZy5DQU5WQVNfRVhUUkE7XG4gICAgICAgICAgICB9KTtcbiAgICB9KTtcblxuICAgIHJldHVybiBteTtcblxufSkoQXVkaW9MZXZlbHMgfHwge30pO1xuXG5tb2R1bGUuZXhwb3J0cyA9IEF1ZGlvTGV2ZWxzOyIsIi8qKlxuICogVXRpbGl0eSBjbGFzcyBmb3IgZHJhd2luZyBjYW52YXMgc2hhcGVzLlxuICovXG52YXIgQ2FudmFzVXRpbCA9IChmdW5jdGlvbihteSkge1xuXG4gICAgLyoqXG4gICAgICogRHJhd3MgYSByb3VuZCByZWN0YW5nbGUgd2l0aCBhIGdsb3cuIFRoZSBnbG93V2lkdGggaW5kaWNhdGVzIHRoZSBkZXB0aFxuICAgICAqIG9mIHRoZSBnbG93LlxuICAgICAqXG4gICAgICogQHBhcmFtIGRyYXdDb250ZXh0IHRoZSBjb250ZXh0IG9mIHRoZSBjYW52YXMgdG8gZHJhdyB0b1xuICAgICAqIEBwYXJhbSB4IHRoZSB4IGNvb3JkaW5hdGUgb2YgdGhlIHJvdW5kIHJlY3RhbmdsZVxuICAgICAqIEBwYXJhbSB5IHRoZSB5IGNvb3JkaW5hdGUgb2YgdGhlIHJvdW5kIHJlY3RhbmdsZVxuICAgICAqIEBwYXJhbSB3IHRoZSB3aWR0aCBvZiB0aGUgcm91bmQgcmVjdGFuZ2xlXG4gICAgICogQHBhcmFtIGggdGhlIGhlaWdodCBvZiB0aGUgcm91bmQgcmVjdGFuZ2xlXG4gICAgICogQHBhcmFtIGdsb3dDb2xvciB0aGUgY29sb3Igb2YgdGhlIGdsb3dcbiAgICAgKiBAcGFyYW0gZ2xvd1dpZHRoIHRoZSB3aWR0aCBvZiB0aGUgZ2xvd1xuICAgICAqL1xuICAgIG15LmRyYXdSb3VuZFJlY3RHbG93XG4gICAgICAgID0gZnVuY3Rpb24oZHJhd0NvbnRleHQsIHgsIHksIHcsIGgsIHIsIGdsb3dDb2xvciwgZ2xvd1dpZHRoKSB7XG5cbiAgICAgICAgLy8gU2F2ZSB0aGUgcHJldmlvdXMgc3RhdGUgb2YgdGhlIGNvbnRleHQuXG4gICAgICAgIGRyYXdDb250ZXh0LnNhdmUoKTtcblxuICAgICAgICBpZiAodyA8IDIgKiByKSByID0gdyAvIDI7XG4gICAgICAgIGlmIChoIDwgMiAqIHIpIHIgPSBoIC8gMjtcblxuICAgICAgICAvLyBEcmF3IGEgcm91bmQgcmVjdGFuZ2xlLlxuICAgICAgICBkcmF3Q29udGV4dC5iZWdpblBhdGgoKTtcbiAgICAgICAgZHJhd0NvbnRleHQubW92ZVRvKHgrciwgeSk7XG4gICAgICAgIGRyYXdDb250ZXh0LmFyY1RvKHgrdywgeSwgICB4K3csIHkraCwgcik7XG4gICAgICAgIGRyYXdDb250ZXh0LmFyY1RvKHgrdywgeStoLCB4LCAgIHkraCwgcik7XG4gICAgICAgIGRyYXdDb250ZXh0LmFyY1RvKHgsICAgeStoLCB4LCAgIHksICAgcik7XG4gICAgICAgIGRyYXdDb250ZXh0LmFyY1RvKHgsICAgeSwgICB4K3csIHksICAgcik7XG4gICAgICAgIGRyYXdDb250ZXh0LmNsb3NlUGF0aCgpO1xuXG4gICAgICAgIC8vIEFkZCBhIHNoYWRvdyBhcm91bmQgdGhlIHJlY3RhbmdsZVxuICAgICAgICBkcmF3Q29udGV4dC5zaGFkb3dDb2xvciA9IGdsb3dDb2xvcjtcbiAgICAgICAgZHJhd0NvbnRleHQuc2hhZG93Qmx1ciA9IGdsb3dXaWR0aDtcbiAgICAgICAgZHJhd0NvbnRleHQuc2hhZG93T2Zmc2V0WCA9IDA7XG4gICAgICAgIGRyYXdDb250ZXh0LnNoYWRvd09mZnNldFkgPSAwO1xuXG4gICAgICAgIC8vIEZpbGwgdGhlIHNoYXBlLlxuICAgICAgICBkcmF3Q29udGV4dC5maWxsKCk7XG5cbiAgICAgICAgZHJhd0NvbnRleHQuc2F2ZSgpO1xuXG4gICAgICAgIGRyYXdDb250ZXh0LnJlc3RvcmUoKTtcblxuLy8gICAgICAxKSBVbmNvbW1lbnQgdGhpcyBsaW5lIHRvIHVzZSBDb21wb3NpdGUgT3BlcmF0aW9uLCB3aGljaCBpcyBkb2luZyB0aGVcbi8vICAgICAgc2FtZSBhcyB0aGUgY2xpcCBmdW5jdGlvbiBiZWxvdyBhbmQgaXMgYWxzbyBhbnRpYWxpYXNpbmcgdGhlIHJvdW5kXG4vLyAgICAgIGJvcmRlciwgYnV0IGlzIHNhaWQgdG8gYmUgbGVzcyBmYXN0IHBlcmZvcm1hbmNlIHdpc2UuXG5cbi8vICAgICAgZHJhd0NvbnRleHQuZ2xvYmFsQ29tcG9zaXRlT3BlcmF0aW9uPSdkZXN0aW5hdGlvbi1vdXQnO1xuXG4gICAgICAgIGRyYXdDb250ZXh0LmJlZ2luUGF0aCgpO1xuICAgICAgICBkcmF3Q29udGV4dC5tb3ZlVG8oeCtyLCB5KTtcbiAgICAgICAgZHJhd0NvbnRleHQuYXJjVG8oeCt3LCB5LCAgIHgrdywgeStoLCByKTtcbiAgICAgICAgZHJhd0NvbnRleHQuYXJjVG8oeCt3LCB5K2gsIHgsICAgeStoLCByKTtcbiAgICAgICAgZHJhd0NvbnRleHQuYXJjVG8oeCwgICB5K2gsIHgsICAgeSwgICByKTtcbiAgICAgICAgZHJhd0NvbnRleHQuYXJjVG8oeCwgICB5LCAgIHgrdywgeSwgICByKTtcbiAgICAgICAgZHJhd0NvbnRleHQuY2xvc2VQYXRoKCk7XG5cbi8vICAgICAgMikgVW5jb21tZW50IHRoaXMgbGluZSB0byB1c2UgQ29tcG9zaXRlIE9wZXJhdGlvbiwgd2hpY2ggaXMgZG9pbmcgdGhlXG4vLyAgICAgIHNhbWUgYXMgdGhlIGNsaXAgZnVuY3Rpb24gYmVsb3cgYW5kIGlzIGFsc28gYW50aWFsaWFzaW5nIHRoZSByb3VuZFxuLy8gICAgICBib3JkZXIsIGJ1dCBpcyBzYWlkIHRvIGJlIGxlc3MgZmFzdCBwZXJmb3JtYW5jZSB3aXNlLlxuXG4vLyAgICAgIGRyYXdDb250ZXh0LmZpbGwoKTtcblxuICAgICAgICAvLyBDb21tZW50IHRoZXNlIHR3byBsaW5lcyBpZiBjaG9vc2luZyB0byBkbyB0aGUgc2FtZSB3aXRoIGNvbXBvc2l0ZVxuICAgICAgICAvLyBvcGVyYXRpb24gYWJvdmUgMSBhbmQgMi5cbiAgICAgICAgZHJhd0NvbnRleHQuY2xpcCgpO1xuICAgICAgICBkcmF3Q29udGV4dC5jbGVhclJlY3QoMCwgMCwgMjc3LCAyMDApO1xuXG4gICAgICAgIC8vIFJlc3RvcmUgdGhlIHByZXZpb3VzIGNvbnRleHQgc3RhdGUuXG4gICAgICAgIGRyYXdDb250ZXh0LnJlc3RvcmUoKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogQ2xvbmVzIHRoZSBnaXZlbiBjYW52YXMuXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIHRoZSBuZXcgY2xvbmVkIGNhbnZhcy5cbiAgICAgKi9cbiAgICBteS5jbG9uZUNhbnZhcyA9IGZ1bmN0aW9uIChvbGRDYW52YXMpIHtcbiAgICAgICAgLypcbiAgICAgICAgICogRklYTUUgVGVzdGluZyBoYXMgc2hvd24gdGhhdCBvbGRDYW52YXMgbWF5IG5vdCBleGlzdC4gSW4gc3VjaCBhIGNhc2UsXG4gICAgICAgICAqIHRoZSBtZXRob2QgQ2FudmFzVXRpbC5jbG9uZUNhbnZhcyBtYXkgdGhyb3cgYW4gZXJyb3IuIFNpbmNlIGF1ZGlvXG4gICAgICAgICAqIGxldmVscyBhcmUgZnJlcXVlbnRseSB1cGRhdGVkLCB0aGUgZXJyb3JzIGhhdmUgYmVlbiBvYnNlcnZlZCB0byBwaWxlXG4gICAgICAgICAqIGludG8gdGhlIGNvbnNvbGUsIHN0cmFpbiB0aGUgQ1BVLlxuICAgICAgICAgKi9cbiAgICAgICAgaWYgKCFvbGRDYW52YXMpXG4gICAgICAgICAgICByZXR1cm4gb2xkQ2FudmFzO1xuXG4gICAgICAgIC8vY3JlYXRlIGEgbmV3IGNhbnZhc1xuICAgICAgICB2YXIgbmV3Q2FudmFzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnY2FudmFzJyk7XG4gICAgICAgIHZhciBjb250ZXh0ID0gbmV3Q2FudmFzLmdldENvbnRleHQoJzJkJyk7XG5cbiAgICAgICAgLy9zZXQgZGltZW5zaW9uc1xuICAgICAgICBuZXdDYW52YXMud2lkdGggPSBvbGRDYW52YXMud2lkdGg7XG4gICAgICAgIG5ld0NhbnZhcy5oZWlnaHQgPSBvbGRDYW52YXMuaGVpZ2h0O1xuXG4gICAgICAgIC8vYXBwbHkgdGhlIG9sZCBjYW52YXMgdG8gdGhlIG5ldyBvbmVcbiAgICAgICAgY29udGV4dC5kcmF3SW1hZ2Uob2xkQ2FudmFzLCAwLCAwKTtcblxuICAgICAgICAvL3JldHVybiB0aGUgbmV3IGNhbnZhc1xuICAgICAgICByZXR1cm4gbmV3Q2FudmFzO1xuICAgIH07XG5cbiAgICByZXR1cm4gbXk7XG59KShDYW52YXNVdGlsIHx8IHt9KTtcblxubW9kdWxlLmV4cG9ydHMgPSBDYW52YXNVdGlsOyIsIi8qIEluaXRpYWwgXCJhdXRoZW50aWNhdGlvbiByZXF1aXJlZFwiIGRpYWxvZyAqL1xudmFyIGF1dGhEaWFsb2cgPSBudWxsO1xuLyogTG9vcCByZXRyeSBJRCB0aGF0IHdpdHMgZm9yIG90aGVyIHVzZXIgdG8gY3JlYXRlIHRoZSByb29tICovXG52YXIgYXV0aFJldHJ5SWQgPSBudWxsO1xudmFyIGF1dGhlbnRpY2F0aW9uV2luZG93ID0gbnVsbDtcblxudmFyIEF1dGhlbnRpY2F0aW9uID0ge1xuICAgIG9wZW5BdXRoZW50aWNhdGlvbkRpYWxvZzogZnVuY3Rpb24gKHJvb21OYW1lLCBpbnRlcnZhbENhbGxiYWNrLCBjYWxsYmFjaykge1xuICAgICAgICAvLyBUaGlzIGlzIHRoZSBsb29wIHRoYXQgd2lsbCB3YWl0IGZvciB0aGUgcm9vbSB0byBiZSBjcmVhdGVkIGJ5XG4gICAgICAgIC8vIHNvbWVvbmUgZWxzZS4gJ2F1dGhfcmVxdWlyZWQubW9kZXJhdG9yJyB3aWxsIGJyaW5nIHVzIGJhY2sgaGVyZS5cbiAgICAgICAgYXV0aFJldHJ5SWQgPSB3aW5kb3cuc2V0VGltZW91dChpbnRlcnZhbENhbGxiYWNrICwgNTAwMCk7XG4gICAgICAgIC8vIFNob3cgcHJvbXB0IG9ubHkgaWYgaXQncyBub3Qgb3BlblxuICAgICAgICBpZiAoYXV0aERpYWxvZyAhPT0gbnVsbCkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIC8vIGV4dHJhY3Qgcm9vbSBuYW1lIGZyb20gJ3Jvb21AbXVjLnNlcnZlci5uZXQnXG4gICAgICAgIHZhciByb29tID0gcm9vbU5hbWUuc3Vic3RyKDAsIHJvb21OYW1lLmluZGV4T2YoJ0AnKSk7XG5cbiAgICAgICAgYXV0aERpYWxvZyA9IG1lc3NhZ2VIYW5kbGVyLm9wZW5EaWFsb2coXG4gICAgICAgICAgICAnU3RvcCcsXG4gICAgICAgICAgICAgICAgJ0F1dGhlbnRpY2F0aW9uIGlzIHJlcXVpcmVkIHRvIGNyZWF0ZSByb29tOjxici8+PGI+JyArIHJvb20gK1xuICAgICAgICAgICAgICAgICc8L2I+PC9icj4gWW91IGNhbiBlaXRoZXIgYXV0aGVudGljYXRlIHRvIGNyZWF0ZSB0aGUgcm9vbSBvciAnICtcbiAgICAgICAgICAgICAgICAnanVzdCB3YWl0IGZvciBzb21lb25lIGVsc2UgdG8gZG8gc28uJyxcbiAgICAgICAgICAgIHRydWUsXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgQXV0aGVudGljYXRlOiAnYXV0aE5vdydcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBmdW5jdGlvbiAob25TdWJtaXRFdmVudCwgc3VibWl0VmFsdWUpIHtcblxuICAgICAgICAgICAgICAgIC8vIERvIG5vdCBjbG9zZSB0aGUgZGlhbG9nIHlldFxuICAgICAgICAgICAgICAgIG9uU3VibWl0RXZlbnQucHJldmVudERlZmF1bHQoKTtcblxuICAgICAgICAgICAgICAgIC8vIE9wZW4gbG9naW4gcG9wdXBcbiAgICAgICAgICAgICAgICBpZiAoc3VibWl0VmFsdWUgPT09ICdhdXRoTm93Jykge1xuICAgICAgICAgICAgICAgICAgICBjYWxsYmFjaygpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgKTtcbiAgICB9LFxuICAgIGNsb3NlQXV0aGVudGljYXRpb25XaW5kb3c6ZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAoYXV0aGVudGljYXRpb25XaW5kb3cpIHtcbiAgICAgICAgICAgIGF1dGhlbnRpY2F0aW9uV2luZG93LmNsb3NlKCk7XG4gICAgICAgICAgICBhdXRoZW50aWNhdGlvbldpbmRvdyA9IG51bGw7XG4gICAgICAgIH1cbiAgICB9LFxuICAgIGZvY3VzQXV0aGVudGljYXRpb25XaW5kb3c6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgLy8gSWYgYXV0aCB3aW5kb3cgZXhpc3RzIGp1c3QgYnJpbmcgaXQgdG8gdGhlIGZyb250XG4gICAgICAgIGlmIChhdXRoZW50aWNhdGlvbldpbmRvdykge1xuICAgICAgICAgICAgYXV0aGVudGljYXRpb25XaW5kb3cuZm9jdXMoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgIH0sXG4gICAgY2xvc2VBdXRoZW50aWNhdGlvbkRpYWxvZzogZnVuY3Rpb24gKCkge1xuICAgICAgICAvLyBDbG9zZSBhdXRoZW50aWNhdGlvbiBkaWFsb2cgaWYgb3BlbmVkXG4gICAgICAgIGlmIChhdXRoRGlhbG9nKSB7XG4gICAgICAgICAgICBVSS5tZXNzYWdlSGFuZGxlci5jbG9zZURpYWxvZygpO1xuICAgICAgICAgICAgYXV0aERpYWxvZyA9IG51bGw7XG4gICAgICAgIH1cbiAgICB9LFxuICAgIGNyZWF0ZUF1dGhlbnRpY2F0aW9uV2luZG93OiBmdW5jdGlvbiAoY2FsbGJhY2ssIHVybCkge1xuICAgICAgICBhdXRoZW50aWNhdGlvbldpbmRvdyA9IG1lc3NhZ2VIYW5kbGVyLm9wZW5DZW50ZXJlZFBvcHVwKFxuICAgICAgICAgICAgdXJsLCA5MTAsIDY2MCxcbiAgICAgICAgICAgIC8vIE9uIGNsb3NlZFxuICAgICAgICAgICAgZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIC8vIENsb3NlIGF1dGhlbnRpY2F0aW9uIGRpYWxvZyBpZiBvcGVuZWRcbiAgICAgICAgICAgICAgICBpZiAoYXV0aERpYWxvZykge1xuICAgICAgICAgICAgICAgICAgICBtZXNzYWdlSGFuZGxlci5jbG9zZURpYWxvZygpO1xuICAgICAgICAgICAgICAgICAgICBhdXRoRGlhbG9nID0gbnVsbDtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgY2FsbGJhY2soKTtcbiAgICAgICAgICAgICAgICBhdXRoZW50aWNhdGlvbldpbmRvdyA9IG51bGw7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIGF1dGhlbnRpY2F0aW9uV2luZG93O1xuICAgIH0sXG4gICAgc3RvcEludGVydmFsOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIC8vIENsZWFyIHJldHJ5IGludGVydmFsLCBzbyB0aGF0IHdlIGRvbid0IGNhbGwgJ2RvSm9pbkFmdGVyRm9jdXMnIHR3aWNlXG4gICAgICAgIGlmIChhdXRoUmV0cnlJZCkge1xuICAgICAgICAgICAgd2luZG93LmNsZWFyVGltZW91dChhdXRoUmV0cnlJZCk7XG4gICAgICAgICAgICBhdXRoUmV0cnlJZCA9IG51bGw7XG4gICAgICAgIH1cbiAgICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEF1dGhlbnRpY2F0aW9uOyIsInZhciBTZXR0aW5ncyA9IHJlcXVpcmUoXCIuLi9zaWRlX3Bhbm5lbHMvc2V0dGluZ3MvU2V0dGluZ3NcIik7XG5cbnZhciB1c2VycyA9IHt9O1xudmFyIGFjdGl2ZVNwZWFrZXJKaWQ7XG5cbmZ1bmN0aW9uIHNldFZpc2liaWxpdHkoc2VsZWN0b3IsIHNob3cpIHtcbiAgICBpZiAoc2VsZWN0b3IgJiYgc2VsZWN0b3IubGVuZ3RoID4gMCkge1xuICAgICAgICBzZWxlY3Rvci5jc3MoXCJ2aXNpYmlsaXR5XCIsIHNob3cgPyBcInZpc2libGVcIiA6IFwiaGlkZGVuXCIpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gaXNVc2VyTXV0ZWQoamlkKSB7XG4gICAgLy8gWFhYKGdwKSB3ZSBtYXkgd2FudCB0byByZW5hbWUgdGhpcyBtZXRob2QgdG8gc29tZXRoaW5nIGxpa2VcbiAgICAvLyBpc1VzZXJTdHJlYW1pbmcsIGZvciBleGFtcGxlLlxuICAgIGlmIChqaWQgJiYgamlkICE9IHhtcHAubXlKaWQoKSkge1xuICAgICAgICB2YXIgcmVzb3VyY2UgPSBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChqaWQpO1xuICAgICAgICBpZiAoIXJlcXVpcmUoXCIuLi92aWRlb2xheW91dC9WaWRlb0xheW91dFwiKS5pc0luTGFzdE4ocmVzb3VyY2UpKSB7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGlmICghUlRDLnJlbW90ZVN0cmVhbXNbamlkXSB8fCAhUlRDLnJlbW90ZVN0cmVhbXNbamlkXVtNZWRpYVN0cmVhbVR5cGUuVklERU9fVFlQRV0pIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIHJldHVybiBSVEMucmVtb3RlU3RyZWFtc1tqaWRdW01lZGlhU3RyZWFtVHlwZS5WSURFT19UWVBFXS5tdXRlZDtcbn1cblxuZnVuY3Rpb24gZ2V0R3JhdmF0YXJVcmwoaWQsIHNpemUpIHtcbiAgICBpZihpZCA9PT0geG1wcC5teUppZCgpIHx8ICFpZCkge1xuICAgICAgICBpZCA9IFNldHRpbmdzLmdldFNldHRpbmdzKCkudWlkO1xuICAgIH1cbiAgICByZXR1cm4gJ2h0dHBzOi8vd3d3LmdyYXZhdGFyLmNvbS9hdmF0YXIvJyArXG4gICAgICAgIE1ENS5oZXhkaWdlc3QoaWQudHJpbSgpLnRvTG93ZXJDYXNlKCkpICtcbiAgICAgICAgXCI/ZD13YXZhdGFyJnNpemU9XCIgKyAoc2l6ZSB8fCBcIjMwXCIpO1xufVxuXG52YXIgQXZhdGFyID0ge1xuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgdXNlcidzIGF2YXRhciBpbiB0aGUgc2V0dGluZ3MgbWVudShpZiBsb2NhbCB1c2VyKSwgY29udGFjdCBsaXN0XG4gICAgICogYW5kIHRodW1ibmFpbFxuICAgICAqIEBwYXJhbSBqaWQgamlkIG9mIHRoZSB1c2VyXG4gICAgICogQHBhcmFtIGlkIGVtYWlsIG9yIHVzZXJJRCB0byBiZSB1c2VkIGFzIGEgaGFzaFxuICAgICAqL1xuICAgIHNldFVzZXJBdmF0YXI6IGZ1bmN0aW9uIChqaWQsIGlkKSB7XG4gICAgICAgIGlmIChpZCkge1xuICAgICAgICAgICAgaWYgKHVzZXJzW2ppZF0gPT09IGlkKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdXNlcnNbamlkXSA9IGlkO1xuICAgICAgICB9XG4gICAgICAgIHZhciB0aHVtYlVybCA9IGdldEdyYXZhdGFyVXJsKHVzZXJzW2ppZF0gfHwgamlkLCAxMDApO1xuICAgICAgICB2YXIgY29udGFjdExpc3RVcmwgPSBnZXRHcmF2YXRhclVybCh1c2Vyc1tqaWRdIHx8IGppZCk7XG4gICAgICAgIHZhciByZXNvdXJjZUppZCA9IFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCk7XG4gICAgICAgIHZhciB0aHVtYm5haWwgPSAkKCcjcGFydGljaXBhbnRfJyArIHJlc291cmNlSmlkKTtcbiAgICAgICAgdmFyIGF2YXRhciA9ICQoJyNhdmF0YXJfJyArIHJlc291cmNlSmlkKTtcblxuICAgICAgICAvLyBzZXQgdGhlIGF2YXRhciBpbiB0aGUgc2V0dGluZ3MgbWVudSBpZiBpdCBpcyBsb2NhbCB1c2VyIGFuZCBnZXQgdGhlXG4gICAgICAgIC8vIGxvY2FsIHZpZGVvIGNvbnRhaW5lclxuICAgICAgICBpZiAoamlkID09PSB4bXBwLm15SmlkKCkpIHtcbiAgICAgICAgICAgICQoJyNhdmF0YXInKS5nZXQoMCkuc3JjID0gdGh1bWJVcmw7XG4gICAgICAgICAgICB0aHVtYm5haWwgPSAkKCcjbG9jYWxWaWRlb0NvbnRhaW5lcicpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhdmF0YXIgaW4gdGhlIGNvbnRhY3QgbGlzdFxuICAgICAgICB2YXIgY29udGFjdCA9ICQoJyMnICsgcmVzb3VyY2VKaWQgKyAnPmltZycpO1xuICAgICAgICBpZiAoY29udGFjdCAmJiBjb250YWN0Lmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIGNvbnRhY3QuZ2V0KDApLnNyYyA9IGNvbnRhY3RMaXN0VXJsO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhdmF0YXIgaW4gdGhlIHRodW1ibmFpbFxuICAgICAgICBpZiAoYXZhdGFyICYmIGF2YXRhci5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBhdmF0YXJbMF0uc3JjID0gdGh1bWJVcmw7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBpZiAodGh1bWJuYWlsICYmIHRodW1ibmFpbC5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgYXZhdGFyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnaW1nJyk7XG4gICAgICAgICAgICAgICAgYXZhdGFyLmlkID0gJ2F2YXRhcl8nICsgcmVzb3VyY2VKaWQ7XG4gICAgICAgICAgICAgICAgYXZhdGFyLmNsYXNzTmFtZSA9ICd1c2VyQXZhdGFyJztcbiAgICAgICAgICAgICAgICBhdmF0YXIuc3JjID0gdGh1bWJVcmw7XG4gICAgICAgICAgICAgICAgdGh1bWJuYWlsLmFwcGVuZChhdmF0YXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy9pZiB0aGUgdXNlciBpcyB0aGUgY3VycmVudCBhY3RpdmUgc3BlYWtlciAtIHVwZGF0ZSB0aGUgYWN0aXZlIHNwZWFrZXJcbiAgICAgICAgLy8gYXZhdGFyXG4gICAgICAgIGlmIChqaWQgPT09IGFjdGl2ZVNwZWFrZXJKaWQpIHtcbiAgICAgICAgICAgIHRoaXMudXBkYXRlQWN0aXZlU3BlYWtlckF2YXRhclNyYyhqaWQpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIEhpZGVzIG9yIHNob3dzIHRoZSB1c2VyJ3MgYXZhdGFyXG4gICAgICogQHBhcmFtIGppZCBqaWQgb2YgdGhlIHVzZXJcbiAgICAgKiBAcGFyYW0gc2hvdyB3aGV0aGVyIHdlIHNob3VsZCBzaG93IHRoZSBhdmF0YXIgb3Igbm90XG4gICAgICogdmlkZW8gYmVjYXVzZSB0aGVyZSBpcyBubyBkb21pbmFudCBzcGVha2VyIGFuZCBubyBmb2N1c2VkIHNwZWFrZXJcbiAgICAgKi9cbiAgICBzaG93VXNlckF2YXRhcjogZnVuY3Rpb24gKGppZCwgc2hvdykge1xuICAgICAgICBpZiAodXNlcnNbamlkXSkge1xuICAgICAgICAgICAgdmFyIHJlc291cmNlSmlkID0gU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKTtcbiAgICAgICAgICAgIHZhciB2aWRlbyA9ICQoJyNwYXJ0aWNpcGFudF8nICsgcmVzb3VyY2VKaWQgKyAnPnZpZGVvJyk7XG4gICAgICAgICAgICB2YXIgYXZhdGFyID0gJCgnI2F2YXRhcl8nICsgcmVzb3VyY2VKaWQpO1xuXG4gICAgICAgICAgICBpZiAoamlkID09PSB4bXBwLm15SmlkKCkpIHtcbiAgICAgICAgICAgICAgICB2aWRlbyA9ICQoJyNsb2NhbFZpZGVvV3JhcHBlcj52aWRlbycpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKHNob3cgPT09IHVuZGVmaW5lZCB8fCBzaG93ID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgc2hvdyA9IGlzVXNlck11dGVkKGppZCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vaWYgdGhlIHVzZXIgaXMgdGhlIGN1cnJlbnRseSBmb2N1c2VkLCB0aGUgZG9taW5hbnQgc3BlYWtlciBvciBpZlxuICAgICAgICAgICAgLy90aGVyZSBpcyBubyBmb2N1c2VkIGFuZCBubyBkb21pbmFudCBzcGVha2VyIGFuZCB0aGUgbGFyZ2UgdmlkZW8gaXNcbiAgICAgICAgICAgIC8vY3VycmVudGx5IHNob3duXG4gICAgICAgICAgICBpZiAoYWN0aXZlU3BlYWtlckppZCA9PT0gamlkICYmIHJlcXVpcmUoXCIuLi92aWRlb2xheW91dC9WaWRlb0xheW91dFwiKS5pc0xhcmdlVmlkZW9PblRvcCgpKSB7XG4gICAgICAgICAgICAgICAgc2V0VmlzaWJpbGl0eSgkKFwiI2xhcmdlVmlkZW9cIiksICFzaG93KTtcbiAgICAgICAgICAgICAgICBzZXRWaXNpYmlsaXR5KCQoJyNhY3RpdmVTcGVha2VyJyksIHNob3cpO1xuICAgICAgICAgICAgICAgIHNldFZpc2liaWxpdHkoYXZhdGFyLCBmYWxzZSk7XG4gICAgICAgICAgICAgICAgc2V0VmlzaWJpbGl0eSh2aWRlbywgZmFsc2UpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBpZiAodmlkZW8gJiYgdmlkZW8ubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgICAgICAgICBzZXRWaXNpYmlsaXR5KHZpZGVvLCAhc2hvdyk7XG4gICAgICAgICAgICAgICAgICAgIHNldFZpc2liaWxpdHkoYXZhdGFyLCBzaG93KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogVXBkYXRlcyB0aGUgc3JjIG9mIHRoZSBhY3RpdmUgc3BlYWtlciBhdmF0YXJcbiAgICAgKiBAcGFyYW0gamlkIG9mIHRoZSBjdXJyZW50IGFjdGl2ZSBzcGVha2VyXG4gICAgICovXG4gICAgdXBkYXRlQWN0aXZlU3BlYWtlckF2YXRhclNyYzogZnVuY3Rpb24gKGppZCkge1xuICAgICAgICBpZiAoIWppZCkge1xuICAgICAgICAgICAgamlkID0geG1wcC5maW5kSmlkRnJvbVJlc291cmNlKFxuICAgICAgICAgICAgICAgIHJlcXVpcmUoXCIuLi92aWRlb2xheW91dC9WaWRlb0xheW91dFwiKS5nZXRMYXJnZVZpZGVvU3RhdGUoKS51c2VyUmVzb3VyY2VKaWQpO1xuICAgICAgICB9XG4gICAgICAgIHZhciBhdmF0YXIgPSAkKFwiI2FjdGl2ZVNwZWFrZXJBdmF0YXJcIilbMF07XG4gICAgICAgIHZhciB1cmwgPSBnZXRHcmF2YXRhclVybCh1c2Vyc1tqaWRdLFxuICAgICAgICAgICAgaW50ZXJmYWNlQ29uZmlnLkFDVElWRV9TUEVBS0VSX0FWQVRBUl9TSVpFKTtcbiAgICAgICAgaWYgKGppZCA9PT0gYWN0aXZlU3BlYWtlckppZCAmJiBhdmF0YXIuc3JjID09PSB1cmwpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBhY3RpdmVTcGVha2VySmlkID0gamlkO1xuICAgICAgICB2YXIgaXNNdXRlZCA9IGlzVXNlck11dGVkKGppZCk7XG4gICAgICAgIGlmIChqaWQgJiYgaXNNdXRlZCAhPT0gbnVsbCkge1xuICAgICAgICAgICAgYXZhdGFyLnNyYyA9IHVybDtcbiAgICAgICAgICAgIHNldFZpc2liaWxpdHkoJChcIiNsYXJnZVZpZGVvXCIpLCAhaXNNdXRlZCk7XG4gICAgICAgICAgICBBdmF0YXIuc2hvd1VzZXJBdmF0YXIoamlkLCBpc011dGVkKTtcbiAgICAgICAgfVxuICAgIH1cblxufTtcblxuXG5tb2R1bGUuZXhwb3J0cyA9IEF2YXRhcjsiLCIvKiBnbG9iYWwgJCwgY29uZmlnLCBkb2NrVG9vbGJhcixcbiAgIHNldExhcmdlVmlkZW9WaXNpYmxlLCBVdGlsICovXG5cbnZhciBWaWRlb0xheW91dCA9IHJlcXVpcmUoXCIuLi92aWRlb2xheW91dC9WaWRlb0xheW91dFwiKTtcbnZhciBQcmV6aSA9IHJlcXVpcmUoXCIuLi9wcmV6aS9QcmV6aVwiKTtcbnZhciBVSVV0aWwgPSByZXF1aXJlKFwiLi4vdXRpbC9VSVV0aWxcIik7XG5cbnZhciBldGhlcnBhZE5hbWUgPSBudWxsO1xudmFyIGV0aGVycGFkSUZyYW1lID0gbnVsbDtcbnZhciBkb21haW4gPSBudWxsO1xudmFyIG9wdGlvbnMgPSBcIj9zaG93Q29udHJvbHM9dHJ1ZSZzaG93Q2hhdD1mYWxzZSZzaG93TGluZU51bWJlcnM9dHJ1ZSZ1c2VNb25vc3BhY2VGb250PWZhbHNlXCI7XG5cblxuLyoqXG4gKiBSZXNpemVzIHRoZSBldGhlcnBhZC5cbiAqL1xuZnVuY3Rpb24gcmVzaXplKCkge1xuICAgIGlmICgkKCcjZXRoZXJwYWQ+aWZyYW1lJykubGVuZ3RoKSB7XG4gICAgICAgIHZhciByZW1vdGVWaWRlb3MgPSAkKCcjcmVtb3RlVmlkZW9zJyk7XG4gICAgICAgIHZhciBhdmFpbGFibGVIZWlnaHRcbiAgICAgICAgICAgID0gd2luZG93LmlubmVySGVpZ2h0IC0gcmVtb3RlVmlkZW9zLm91dGVySGVpZ2h0KCk7XG4gICAgICAgIHZhciBhdmFpbGFibGVXaWR0aCA9IFVJVXRpbC5nZXRBdmFpbGFibGVWaWRlb1dpZHRoKCk7XG5cbiAgICAgICAgJCgnI2V0aGVycGFkPmlmcmFtZScpLndpZHRoKGF2YWlsYWJsZVdpZHRoKTtcbiAgICAgICAgJCgnI2V0aGVycGFkPmlmcmFtZScpLmhlaWdodChhdmFpbGFibGVIZWlnaHQpO1xuICAgIH1cbn1cblxuLyoqXG4gKiBTaGFyZXMgdGhlIEV0aGVycGFkIG5hbWUgd2l0aCBvdGhlciBwYXJ0aWNpcGFudHMuXG4gKi9cbmZ1bmN0aW9uIHNoYXJlRXRoZXJwYWQoKSB7XG4gICAgeG1wcC5hZGRUb1ByZXNlbmNlKFwiZXRoZXJwYWRcIiwgZXRoZXJwYWROYW1lKTtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIHRoZSBFdGhlcnBhZCBidXR0b24gYW5kIGFkZHMgaXQgdG8gdGhlIHRvb2xiYXIuXG4gKi9cbmZ1bmN0aW9uIGVuYWJsZUV0aGVycGFkQnV0dG9uKCkge1xuICAgIGlmICghJCgnI2V0aGVycGFkQnV0dG9uJykuaXMoXCI6dmlzaWJsZVwiKSlcbiAgICAgICAgJCgnI2V0aGVycGFkQnV0dG9uJykuY3NzKHtkaXNwbGF5OiAnaW5saW5lLWJsb2NrJ30pO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgdGhlIElGcmFtZSBmb3IgdGhlIGV0aGVycGFkLlxuICovXG5mdW5jdGlvbiBjcmVhdGVJRnJhbWUoKSB7XG4gICAgZXRoZXJwYWRJRnJhbWUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdpZnJhbWUnKTtcbiAgICBldGhlcnBhZElGcmFtZS5zcmMgPSBkb21haW4gKyBldGhlcnBhZE5hbWUgKyBvcHRpb25zO1xuICAgIGV0aGVycGFkSUZyYW1lLmZyYW1lQm9yZGVyID0gMDtcbiAgICBldGhlcnBhZElGcmFtZS5zY3JvbGxpbmcgPSBcIm5vXCI7XG4gICAgZXRoZXJwYWRJRnJhbWUud2lkdGggPSAkKCcjbGFyZ2VWaWRlb0NvbnRhaW5lcicpLndpZHRoKCkgfHwgNjQwO1xuICAgIGV0aGVycGFkSUZyYW1lLmhlaWdodCA9ICQoJyNsYXJnZVZpZGVvQ29udGFpbmVyJykuaGVpZ2h0KCkgfHwgNDgwO1xuICAgIGV0aGVycGFkSUZyYW1lLnNldEF0dHJpYnV0ZSgnc3R5bGUnLCAndmlzaWJpbGl0eTogaGlkZGVuOycpO1xuXG4gICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2V0aGVycGFkJykuYXBwZW5kQ2hpbGQoZXRoZXJwYWRJRnJhbWUpO1xuXG4gICAgZXRoZXJwYWRJRnJhbWUub25sb2FkID0gZnVuY3Rpb24oKSB7XG5cbiAgICAgICAgZG9jdW1lbnQuZG9tYWluID0gZG9jdW1lbnQuZG9tYWluO1xuICAgICAgICBidWJibGVJZnJhbWVNb3VzZU1vdmUoZXRoZXJwYWRJRnJhbWUpO1xuICAgICAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgLy8gdGhlIGlmcmFtZXMgaW5zaWRlIG9mIHRoZSBldGhlcnBhZCBhcmVcbiAgICAgICAgICAgIC8vIG5vdCB5ZXQgbG9hZGVkIHdoZW4gdGhlIGV0aGVycGFkIGlmcmFtZSBpcyBsb2FkZWRcbiAgICAgICAgICAgIHZhciBvdXRlciA9IGV0aGVycGFkSUZyYW1lLlxuICAgICAgICAgICAgICAgIGNvbnRlbnREb2N1bWVudC5nZXRFbGVtZW50c0J5TmFtZShcImFjZV9vdXRlclwiKVswXTtcbiAgICAgICAgICAgIGJ1YmJsZUlmcmFtZU1vdXNlTW92ZShvdXRlcik7XG4gICAgICAgICAgICB2YXIgaW5uZXIgPSBvdXRlci5cbiAgICAgICAgICAgICAgICBjb250ZW50RG9jdW1lbnQuZ2V0RWxlbWVudHNCeU5hbWUoXCJhY2VfaW5uZXJcIilbMF07XG4gICAgICAgICAgICBidWJibGVJZnJhbWVNb3VzZU1vdmUoaW5uZXIpO1xuICAgICAgICB9LCAyMDAwKTtcbiAgICB9O1xufVxuXG5mdW5jdGlvbiBidWJibGVJZnJhbWVNb3VzZU1vdmUoaWZyYW1lKXtcbiAgICB2YXIgZXhpc3RpbmdPbk1vdXNlTW92ZSA9IGlmcmFtZS5jb250ZW50V2luZG93Lm9ubW91c2Vtb3ZlO1xuICAgIGlmcmFtZS5jb250ZW50V2luZG93Lm9ubW91c2Vtb3ZlID0gZnVuY3Rpb24oZSl7XG4gICAgICAgIGlmKGV4aXN0aW5nT25Nb3VzZU1vdmUpIGV4aXN0aW5nT25Nb3VzZU1vdmUoZSk7XG4gICAgICAgIHZhciBldnQgPSBkb2N1bWVudC5jcmVhdGVFdmVudChcIk1vdXNlRXZlbnRzXCIpO1xuICAgICAgICB2YXIgYm91bmRpbmdDbGllbnRSZWN0ID0gaWZyYW1lLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICBldnQuaW5pdE1vdXNlRXZlbnQoXG4gICAgICAgICAgICBcIm1vdXNlbW92ZVwiLFxuICAgICAgICAgICAgdHJ1ZSwgLy8gYnViYmxlc1xuICAgICAgICAgICAgZmFsc2UsIC8vIG5vdCBjYW5jZWxhYmxlXG4gICAgICAgICAgICB3aW5kb3csXG4gICAgICAgICAgICBlLmRldGFpbCxcbiAgICAgICAgICAgIGUuc2NyZWVuWCxcbiAgICAgICAgICAgIGUuc2NyZWVuWSxcbiAgICAgICAgICAgICAgICBlLmNsaWVudFggKyBib3VuZGluZ0NsaWVudFJlY3QubGVmdCxcbiAgICAgICAgICAgICAgICBlLmNsaWVudFkgKyBib3VuZGluZ0NsaWVudFJlY3QudG9wLFxuICAgICAgICAgICAgZS5jdHJsS2V5LFxuICAgICAgICAgICAgZS5hbHRLZXksXG4gICAgICAgICAgICBlLnNoaWZ0S2V5LFxuICAgICAgICAgICAgZS5tZXRhS2V5LFxuICAgICAgICAgICAgZS5idXR0b24sXG4gICAgICAgICAgICBudWxsIC8vIG5vIHJlbGF0ZWQgZWxlbWVudFxuICAgICAgICApO1xuICAgICAgICBpZnJhbWUuZGlzcGF0Y2hFdmVudChldnQpO1xuICAgIH07XG59XG5cblxuLyoqXG4gKiBPbiB2aWRlbyBzZWxlY3RlZCBldmVudC5cbiAqL1xuJChkb2N1bWVudCkuYmluZCgndmlkZW8uc2VsZWN0ZWQnLCBmdW5jdGlvbiAoZXZlbnQsIGlzUHJlc2VudGF0aW9uKSB7XG4gICAgaWYgKGNvbmZpZy5ldGhlcnBhZF9iYXNlICYmIGV0aGVycGFkSUZyYW1lICYmIGV0aGVycGFkSUZyYW1lLnN0eWxlLnZpc2liaWxpdHkgIT09ICdoaWRkZW4nKVxuICAgICAgICBFdGhlcnBhZC50b2dnbGVFdGhlcnBhZChpc1ByZXNlbnRhdGlvbik7XG59KTtcblxuXG52YXIgRXRoZXJwYWQgPSB7XG4gICAgLyoqXG4gICAgICogSW5pdGlhbGl6ZXMgdGhlIGV0aGVycGFkLlxuICAgICAqL1xuICAgIGluaXQ6IGZ1bmN0aW9uIChuYW1lKSB7XG5cbiAgICAgICAgaWYgKGNvbmZpZy5ldGhlcnBhZF9iYXNlICYmICFldGhlcnBhZE5hbWUpIHtcblxuICAgICAgICAgICAgZG9tYWluID0gY29uZmlnLmV0aGVycGFkX2Jhc2U7XG5cbiAgICAgICAgICAgIGlmICghbmFtZSkge1xuICAgICAgICAgICAgICAgIC8vIEluIGNhc2Ugd2UncmUgdGhlIGZvY3VzIHdlIGdlbmVyYXRlIHRoZSBuYW1lLlxuICAgICAgICAgICAgICAgIGV0aGVycGFkTmFtZSA9IE1hdGgucmFuZG9tKCkudG9TdHJpbmcoMzYpLnN1YnN0cmluZyg3KSArXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdfJyArIChuZXcgRGF0ZSgpLmdldFRpbWUoKSkudG9TdHJpbmcoKTtcbiAgICAgICAgICAgICAgICBzaGFyZUV0aGVycGFkKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgZXRoZXJwYWROYW1lID0gbmFtZTtcblxuICAgICAgICAgICAgZW5hYmxlRXRoZXJwYWRCdXR0b24oKTtcblxuICAgICAgICAgICAgLyoqXG4gICAgICAgICAgICAgKiBSZXNpemVzIHRoZSBldGhlcnBhZCwgd2hlbiB0aGUgd2luZG93IGlzIHJlc2l6ZWQuXG4gICAgICAgICAgICAgKi9cbiAgICAgICAgICAgICQod2luZG93KS5yZXNpemUoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIHJlc2l6ZSgpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogT3BlbnMvaGlkZXMgdGhlIEV0aGVycGFkLlxuICAgICAqL1xuICAgIHRvZ2dsZUV0aGVycGFkOiBmdW5jdGlvbiAoaXNQcmVzZW50YXRpb24pIHtcbiAgICAgICAgaWYgKCFldGhlcnBhZElGcmFtZSlcbiAgICAgICAgICAgIGNyZWF0ZUlGcmFtZSgpO1xuXG4gICAgICAgIHZhciBsYXJnZVZpZGVvID0gbnVsbDtcbiAgICAgICAgaWYgKFByZXppLmlzUHJlc2VudGF0aW9uVmlzaWJsZSgpKVxuICAgICAgICAgICAgbGFyZ2VWaWRlbyA9ICQoJyNwcmVzZW50YXRpb24+aWZyYW1lJyk7XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIGxhcmdlVmlkZW8gPSAkKCcjbGFyZ2VWaWRlbycpO1xuXG4gICAgICAgIGlmICgkKCcjZXRoZXJwYWQ+aWZyYW1lJykuY3NzKCd2aXNpYmlsaXR5JykgPT09ICdoaWRkZW4nKSB7XG4gICAgICAgICAgICAkKCcjYWN0aXZlU3BlYWtlcicpLmNzcygndmlzaWJpbGl0eScsICdoaWRkZW4nKTtcbiAgICAgICAgICAgIGxhcmdlVmlkZW8uZmFkZU91dCgzMDAsIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICBpZiAoUHJlemkuaXNQcmVzZW50YXRpb25WaXNpYmxlKCkpIHtcbiAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlby5jc3Moe29wYWNpdHk6ICcwJ30pO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LnNldExhcmdlVmlkZW9WaXNpYmxlKGZhbHNlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgJCgnI2V0aGVycGFkPmlmcmFtZScpLmZhZGVJbigzMDAsIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICBkb2N1bWVudC5ib2R5LnN0eWxlLmJhY2tncm91bmQgPSAnI2VlZWVlZSc7XG4gICAgICAgICAgICAgICAgJCgnI2V0aGVycGFkPmlmcmFtZScpLmNzcyh7dmlzaWJpbGl0eTogJ3Zpc2libGUnfSk7XG4gICAgICAgICAgICAgICAgJCgnI2V0aGVycGFkJykuY3NzKHt6SW5kZXg6IDJ9KTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKCQoJyNldGhlcnBhZD5pZnJhbWUnKSkge1xuICAgICAgICAgICAgJCgnI2V0aGVycGFkPmlmcmFtZScpLmZhZGVPdXQoMzAwLCBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgJCgnI2V0aGVycGFkPmlmcmFtZScpLmNzcyh7dmlzaWJpbGl0eTogJ2hpZGRlbid9KTtcbiAgICAgICAgICAgICAgICAkKCcjZXRoZXJwYWQnKS5jc3Moe3pJbmRleDogMH0pO1xuICAgICAgICAgICAgICAgIGRvY3VtZW50LmJvZHkuc3R5bGUuYmFja2dyb3VuZCA9ICdibGFjayc7XG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgaWYgKCFpc1ByZXNlbnRhdGlvbikge1xuICAgICAgICAgICAgICAgICQoJyNsYXJnZVZpZGVvJykuZmFkZUluKDMwMCwgZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgICAgICBWaWRlb0xheW91dC5zZXRMYXJnZVZpZGVvVmlzaWJsZSh0cnVlKTtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXNpemUoKTtcbiAgICB9LFxuXG4gICAgaXNWaXNpYmxlOiBmdW5jdGlvbigpIHtcbiAgICAgICAgdmFyIGV0aGVycGFkSWZyYW1lID0gJCgnI2V0aGVycGFkPmlmcmFtZScpO1xuICAgICAgICByZXR1cm4gZXRoZXJwYWRJZnJhbWUgJiYgZXRoZXJwYWRJZnJhbWUuaXMoJzp2aXNpYmxlJyk7XG4gICAgfVxuXG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEV0aGVycGFkO1xuIiwidmFyIFRvb2xiYXJUb2dnbGVyID0gcmVxdWlyZShcIi4uL3Rvb2xiYXJzL1Rvb2xiYXJUb2dnbGVyXCIpO1xudmFyIFVJVXRpbCA9IHJlcXVpcmUoXCIuLi91dGlsL1VJVXRpbFwiKTtcbnZhciBWaWRlb0xheW91dCA9IHJlcXVpcmUoXCIuLi92aWRlb2xheW91dC9WaWRlb0xheW91dFwiKTtcbnZhciBtZXNzYWdlSGFuZGxlciA9IHJlcXVpcmUoXCIuLi91dGlsL01lc3NhZ2VIYW5kbGVyXCIpO1xuXG52YXIgcHJlemlQbGF5ZXIgPSBudWxsO1xuXG52YXIgUHJlemkgPSB7XG5cblxuICAgIC8qKlxuICAgICAqIFJlbG9hZHMgdGhlIGN1cnJlbnQgcHJlc2VudGF0aW9uLlxuICAgICAqL1xuICAgIHJlbG9hZFByZXNlbnRhdGlvbjogZnVuY3Rpb24oKSB7XG4gICAgICAgIHZhciBpZnJhbWUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChwcmV6aVBsYXllci5vcHRpb25zLnByZXppSWQpO1xuICAgICAgICBpZnJhbWUuc3JjID0gaWZyYW1lLnNyYztcbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyA8dHQ+dHJ1ZTwvdHQ+IGlmIHRoZSBwcmVzZW50YXRpb24gaXMgdmlzaWJsZSwgPHR0PmZhbHNlPC90dD4gLVxuICAgICAqIG90aGVyd2lzZS5cbiAgICAgKi9cbiAgICBpc1ByZXNlbnRhdGlvblZpc2libGU6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuICgkKCcjcHJlc2VudGF0aW9uPmlmcmFtZScpICE9IG51bGxcbiAgICAgICAgICAgICAgICAmJiAkKCcjcHJlc2VudGF0aW9uPmlmcmFtZScpLmNzcygnb3BhY2l0eScpID09IDEpO1xuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBPcGVucyB0aGUgUHJlemkgZGlhbG9nLCBmcm9tIHdoaWNoIHRoZSB1c2VyIGNvdWxkIGNob29zZSBhIHByZXNlbnRhdGlvblxuICAgICAqIHRvIGxvYWQuXG4gICAgICovXG4gICAgb3BlblByZXppRGlhbG9nOiBmdW5jdGlvbigpIHtcbiAgICAgICAgdmFyIG15cHJlemkgPSB4bXBwLmdldFByZXppKCk7XG4gICAgICAgIGlmIChteXByZXppKSB7XG4gICAgICAgICAgICBtZXNzYWdlSGFuZGxlci5vcGVuVHdvQnV0dG9uRGlhbG9nKFwiUmVtb3ZlIFByZXppXCIsXG4gICAgICAgICAgICAgICAgXCJBcmUgeW91IHN1cmUgeW91IHdvdWxkIGxpa2UgdG8gcmVtb3ZlIHlvdXIgUHJlemk/XCIsXG4gICAgICAgICAgICAgICAgZmFsc2UsXG4gICAgICAgICAgICAgICAgXCJSZW1vdmVcIixcbiAgICAgICAgICAgICAgICBmdW5jdGlvbihlLHYsbSxmKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmKHYpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHhtcHAucmVtb3ZlUHJlemlGcm9tUHJlc2VuY2UoKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAocHJlemlQbGF5ZXIgIT0gbnVsbCkge1xuICAgICAgICAgICAgbWVzc2FnZUhhbmRsZXIub3BlblR3b0J1dHRvbkRpYWxvZyhcIlNoYXJlIGEgUHJlemlcIixcbiAgICAgICAgICAgICAgICBcIkFub3RoZXIgcGFydGljaXBhbnQgaXMgYWxyZWFkeSBzaGFyaW5nIGEgUHJlemkuXCIgK1xuICAgICAgICAgICAgICAgICAgICBcIlRoaXMgY29uZmVyZW5jZSBhbGxvd3Mgb25seSBvbmUgUHJlemkgYXQgYSB0aW1lLlwiLFxuICAgICAgICAgICAgICAgIGZhbHNlLFxuICAgICAgICAgICAgICAgIFwiT2tcIixcbiAgICAgICAgICAgICAgICBmdW5jdGlvbihlLHYsbSxmKSB7XG4gICAgICAgICAgICAgICAgICAgICQucHJvbXB0LmNsb3NlKCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHZhciBvcGVuUHJlemlTdGF0ZSA9IHtcbiAgICAgICAgICAgICAgICBzdGF0ZTA6IHtcbiAgICAgICAgICAgICAgICAgICAgaHRtbDogICAnPGgyPlNoYXJlIGEgUHJlemk8L2gyPicgK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICc8aW5wdXQgaWQ9XCJwcmV6aVVybFwiIHR5cGU9XCJ0ZXh0XCIgJyArXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3BsYWNlaG9sZGVyPVwiZS5nLiAnICtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnaHR0cDovL3ByZXppLmNvbS93ejd2aGp5Y2w3ZTYvbXktcHJlemlcIiBhdXRvZm9jdXM+JyxcbiAgICAgICAgICAgICAgICAgICAgcGVyc2lzdGVudDogZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgIGJ1dHRvbnM6IHsgXCJTaGFyZVwiOiB0cnVlICwgXCJDYW5jZWxcIjogZmFsc2V9LFxuICAgICAgICAgICAgICAgICAgICBkZWZhdWx0QnV0dG9uOiAxLFxuICAgICAgICAgICAgICAgICAgICBzdWJtaXQ6IGZ1bmN0aW9uKGUsdixtLGYpe1xuICAgICAgICAgICAgICAgICAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYodilcbiAgICAgICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXIgcHJlemlVcmwgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgncHJlemlVcmwnKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChwcmV6aVVybC52YWx1ZSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciB1cmxWYWx1ZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPSBlbmNvZGVVUkkoVXRpbC5lc2NhcGVIdG1sKHByZXppVXJsLnZhbHVlKSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHVybFZhbHVlLmluZGV4T2YoJ2h0dHA6Ly9wcmV6aS5jb20vJykgIT0gMFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgdXJsVmFsdWUuaW5kZXhPZignaHR0cHM6Ly9wcmV6aS5jb20vJykgIT0gMClcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJC5wcm9tcHQuZ29Ub1N0YXRlKCdzdGF0ZTEnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBwcmVzSWRUbXAgPSB1cmxWYWx1ZS5zdWJzdHJpbmcoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVybFZhbHVlLmluZGV4T2YoXCJwcmV6aS5jb20vXCIpICsgMTApO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFpc0FscGhhbnVtZXJpYyhwcmVzSWRUbXApXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHx8IHByZXNJZFRtcC5pbmRleE9mKCcvJykgPCAyKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJC5wcm9tcHQuZ29Ub1N0YXRlKCdzdGF0ZTEnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4bXBwLmFkZFRvUHJlc2VuY2UoXCJwcmV6aVwiLCB1cmxWYWx1ZSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJC5wcm9tcHQuY2xvc2UoKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAkLnByb21wdC5jbG9zZSgpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICBzdGF0ZTE6IHtcbiAgICAgICAgICAgICAgICAgICAgaHRtbDogICAnPGgyPlNoYXJlIGEgUHJlemk8L2gyPicgK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICdQbGVhc2UgcHJvdmlkZSBhIGNvcnJlY3QgcHJlemkgbGluay4nLFxuICAgICAgICAgICAgICAgICAgICBwZXJzaXN0ZW50OiBmYWxzZSxcbiAgICAgICAgICAgICAgICAgICAgYnV0dG9uczogeyBcIkJhY2tcIjogdHJ1ZSwgXCJDYW5jZWxcIjogZmFsc2UgfSxcbiAgICAgICAgICAgICAgICAgICAgZGVmYXVsdEJ1dHRvbjogMSxcbiAgICAgICAgICAgICAgICAgICAgc3VibWl0OmZ1bmN0aW9uKGUsdixtLGYpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmKHY9PTApXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgJC5wcm9tcHQuY2xvc2UoKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAkLnByb21wdC5nb1RvU3RhdGUoJ3N0YXRlMCcpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIHZhciBmb2N1c1ByZXppVXJsID0gIGZ1bmN0aW9uKGUpIHtcbiAgICAgICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ByZXppVXJsJykuZm9jdXMoKTtcbiAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgbWVzc2FnZUhhbmRsZXIub3BlbkRpYWxvZ1dpdGhTdGF0ZXMob3BlblByZXppU3RhdGUsIGZvY3VzUHJlemlVcmwsIGZvY3VzUHJlemlVcmwpO1xuICAgICAgICB9XG4gICAgfVxuXG59O1xuXG4vKipcbiAqIEEgbmV3IHByZXNlbnRhdGlvbiBoYXMgYmVlbiBhZGRlZC5cbiAqXG4gKiBAcGFyYW0gZXZlbnQgdGhlIGV2ZW50IGluZGljYXRpbmcgdGhlIGFkZCBvZiBhIHByZXNlbnRhdGlvblxuICogQHBhcmFtIGppZCB0aGUgamlkIGZyb20gd2hpY2ggdGhlIHByZXNlbnRhdGlvbiB3YXMgYWRkZWRcbiAqIEBwYXJhbSBwcmVzVXJsIHVybCBvZiB0aGUgcHJlc2VudGF0aW9uXG4gKiBAcGFyYW0gY3VycmVudFNsaWRlIHRoZSBjdXJyZW50IHNsaWRlIHRvIHdoaWNoIHdlIHNob3VsZCBtb3ZlXG4gKi9cbmZ1bmN0aW9uIHByZXNlbnRhdGlvbkFkZGVkKGV2ZW50LCBqaWQsIHByZXNVcmwsIGN1cnJlbnRTbGlkZSkge1xuICAgIGNvbnNvbGUubG9nKFwicHJlc2VudGF0aW9uIGFkZGVkXCIsIHByZXNVcmwpO1xuXG4gICAgdmFyIHByZXNJZCA9IGdldFByZXNlbnRhdGlvbklkKHByZXNVcmwpO1xuXG4gICAgdmFyIGVsZW1lbnRJZCA9ICdwYXJ0aWNpcGFudF8nXG4gICAgICAgICsgU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKVxuICAgICAgICArICdfJyArIHByZXNJZDtcblxuICAgIC8vIFdlIGV4cGxpY2l0bHkgZG9uJ3Qgc3BlY2lmeSB0aGUgcGVlciBqaWQgaGVyZSwgYmVjYXVzZSB3ZSBkb24ndCB3YW50XG4gICAgLy8gdGhpcyB2aWRlbyB0byBiZSBkZWFsdCB3aXRoIGFzIGEgcGVlciByZWxhdGVkIG9uZSAoZm9yIGV4YW1wbGUgd2VcbiAgICAvLyBkb24ndCB3YW50IHRvIHNob3cgYSBtdXRlL2tpY2sgbWVudSBmb3IgdGhpcyBvbmUsIGV0Yy4pLlxuICAgIFZpZGVvTGF5b3V0LmFkZFJlbW90ZVZpZGVvQ29udGFpbmVyKG51bGwsIGVsZW1lbnRJZCk7XG4gICAgVmlkZW9MYXlvdXQucmVzaXplVGh1bWJuYWlscygpO1xuXG4gICAgdmFyIGNvbnRyb2xzRW5hYmxlZCA9IGZhbHNlO1xuICAgIGlmIChqaWQgPT09IHhtcHAubXlKaWQoKSlcbiAgICAgICAgY29udHJvbHNFbmFibGVkID0gdHJ1ZTtcblxuICAgIHNldFByZXNlbnRhdGlvblZpc2libGUodHJ1ZSk7XG4gICAgJCgnI2xhcmdlVmlkZW9Db250YWluZXInKS5ob3ZlcihcbiAgICAgICAgZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgICAgICBpZiAoUHJlemkuaXNQcmVzZW50YXRpb25WaXNpYmxlKCkpIHtcbiAgICAgICAgICAgICAgICB2YXIgcmVsb2FkQnV0dG9uUmlnaHQgPSB3aW5kb3cuaW5uZXJXaWR0aFxuICAgICAgICAgICAgICAgICAgICAtICQoJyNwcmVzZW50YXRpb24+aWZyYW1lJykub2Zmc2V0KCkubGVmdFxuICAgICAgICAgICAgICAgICAgICAtICQoJyNwcmVzZW50YXRpb24+aWZyYW1lJykud2lkdGgoKTtcblxuICAgICAgICAgICAgICAgICQoJyNyZWxvYWRQcmVzZW50YXRpb24nKS5jc3MoeyAgcmlnaHQ6IHJlbG9hZEJ1dHRvblJpZ2h0LFxuICAgICAgICAgICAgICAgICAgICBkaXNwbGF5OidpbmxpbmUtYmxvY2snfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICAgICAgaWYgKCFQcmV6aS5pc1ByZXNlbnRhdGlvblZpc2libGUoKSlcbiAgICAgICAgICAgICAgICAkKCcjcmVsb2FkUHJlc2VudGF0aW9uJykuY3NzKHtkaXNwbGF5Oidub25lJ30pO1xuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgdmFyIGUgPSBldmVudC50b0VsZW1lbnQgfHwgZXZlbnQucmVsYXRlZFRhcmdldDtcblxuICAgICAgICAgICAgICAgIGlmIChlICYmIGUuaWQgIT0gJ3JlbG9hZFByZXNlbnRhdGlvbicgJiYgZS5pZCAhPSAnaGVhZGVyJylcbiAgICAgICAgICAgICAgICAgICAgJCgnI3JlbG9hZFByZXNlbnRhdGlvbicpLmNzcyh7ZGlzcGxheTonbm9uZSd9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICBwcmV6aVBsYXllciA9IG5ldyBQcmV6aVBsYXllcihcbiAgICAgICAgJ3ByZXNlbnRhdGlvbicsXG4gICAgICAgIHtwcmV6aUlkOiBwcmVzSWQsXG4gICAgICAgICAgICB3aWR0aDogZ2V0UHJlc2VudGF0aW9uV2lkdGgoKSxcbiAgICAgICAgICAgIGhlaWdodDogZ2V0UHJlc2VudGF0aW9uSGVpaGd0KCksXG4gICAgICAgICAgICBjb250cm9sczogY29udHJvbHNFbmFibGVkLFxuICAgICAgICAgICAgZGVidWc6IHRydWVcbiAgICAgICAgfSk7XG5cbiAgICAkKCcjcHJlc2VudGF0aW9uPmlmcmFtZScpLmF0dHIoJ2lkJywgcHJlemlQbGF5ZXIub3B0aW9ucy5wcmV6aUlkKTtcblxuICAgIHByZXppUGxheWVyLm9uKFByZXppUGxheWVyLkVWRU5UX1NUQVRVUywgZnVuY3Rpb24oZXZlbnQpIHtcbiAgICAgICAgY29uc29sZS5sb2coXCJwcmV6aSBzdGF0dXNcIiwgZXZlbnQudmFsdWUpO1xuICAgICAgICBpZiAoZXZlbnQudmFsdWUgPT0gUHJlemlQbGF5ZXIuU1RBVFVTX0NPTlRFTlRfUkVBRFkpIHtcbiAgICAgICAgICAgIGlmIChqaWQgIT0geG1wcC5teUppZCgpKVxuICAgICAgICAgICAgICAgIHByZXppUGxheWVyLmZseVRvU3RlcChjdXJyZW50U2xpZGUpO1xuICAgICAgICB9XG4gICAgfSk7XG5cbiAgICBwcmV6aVBsYXllci5vbihQcmV6aVBsYXllci5FVkVOVF9DVVJSRU5UX1NURVAsIGZ1bmN0aW9uKGV2ZW50KSB7XG4gICAgICAgIGNvbnNvbGUubG9nKFwiZXZlbnQgdmFsdWVcIiwgZXZlbnQudmFsdWUpO1xuICAgICAgICB4bXBwLmFkZFRvUHJlc2VuY2UoXCJwcmV6aVNsaWRlXCIsIGV2ZW50LnZhbHVlKTtcbiAgICB9KTtcblxuICAgICQoXCIjXCIgKyBlbGVtZW50SWQpLmNzcyggJ2JhY2tncm91bmQtaW1hZ2UnLFxuICAgICAgICAndXJsKC4uL2ltYWdlcy9hdmF0YXJwcmV6aS5wbmcpJyk7XG4gICAgJChcIiNcIiArIGVsZW1lbnRJZCkuY2xpY2soXG4gICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHNldFByZXNlbnRhdGlvblZpc2libGUodHJ1ZSk7XG4gICAgICAgIH1cbiAgICApO1xufTtcblxuLyoqXG4gKiBBIHByZXNlbnRhdGlvbiBoYXMgYmVlbiByZW1vdmVkLlxuICpcbiAqIEBwYXJhbSBldmVudCB0aGUgZXZlbnQgaW5kaWNhdGluZyB0aGUgcmVtb3ZlIG9mIGEgcHJlc2VudGF0aW9uXG4gKiBAcGFyYW0gamlkIHRoZSBqaWQgZm9yIHdoaWNoIHRoZSBwcmVzZW50YXRpb24gd2FzIHJlbW92ZWRcbiAqIEBwYXJhbSB0aGUgdXJsIG9mIHRoZSBwcmVzZW50YXRpb25cbiAqL1xuZnVuY3Rpb24gcHJlc2VudGF0aW9uUmVtb3ZlZChldmVudCwgamlkLCBwcmVzVXJsKSB7XG4gICAgY29uc29sZS5sb2coJ3ByZXNlbnRhdGlvbiByZW1vdmVkJywgcHJlc1VybCk7XG4gICAgdmFyIHByZXNJZCA9IGdldFByZXNlbnRhdGlvbklkKHByZXNVcmwpO1xuICAgIHNldFByZXNlbnRhdGlvblZpc2libGUoZmFsc2UpO1xuICAgICQoJyNwYXJ0aWNpcGFudF8nXG4gICAgICAgICsgU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKVxuICAgICAgICArICdfJyArIHByZXNJZCkucmVtb3ZlKCk7XG4gICAgJCgnI3ByZXNlbnRhdGlvbj5pZnJhbWUnKS5yZW1vdmUoKTtcbiAgICBpZiAocHJlemlQbGF5ZXIgIT0gbnVsbCkge1xuICAgICAgICBwcmV6aVBsYXllci5kZXN0cm95KCk7XG4gICAgICAgIHByZXppUGxheWVyID0gbnVsbDtcbiAgICB9XG59O1xuXG4vKipcbiAqIEluZGljYXRlcyBpZiB0aGUgZ2l2ZW4gc3RyaW5nIGlzIGFuIGFscGhhbnVtZXJpYyBzdHJpbmcuXG4gKiBOb3RlIHRoYXQgc29tZSBzcGVjaWFsIGNoYXJhY3RlcnMgYXJlIGFsc28gYWxsb3dlZCAoLSwgXyAsIC8sICYsID8sID0sIDspIGZvciB0aGVcbiAqIHB1cnBvc2Ugb2YgY2hlY2tpbmcgVVJJcy5cbiAqL1xuZnVuY3Rpb24gaXNBbHBoYW51bWVyaWModW5zYWZlVGV4dCkge1xuICAgIHZhciByZWdleCA9IC9eW2EtejAtOS1fXFwvJlxcPz07XSskL2k7XG4gICAgcmV0dXJuIHJlZ2V4LnRlc3QodW5zYWZlVGV4dCk7XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgcHJlc2VudGF0aW9uIGlkIGZyb20gdGhlIGdpdmVuIHVybC5cbiAqL1xuZnVuY3Rpb24gZ2V0UHJlc2VudGF0aW9uSWQgKHByZXNVcmwpIHtcbiAgICB2YXIgcHJlc0lkVG1wID0gcHJlc1VybC5zdWJzdHJpbmcocHJlc1VybC5pbmRleE9mKFwicHJlemkuY29tL1wiKSArIDEwKTtcbiAgICByZXR1cm4gcHJlc0lkVG1wLnN1YnN0cmluZygwLCBwcmVzSWRUbXAuaW5kZXhPZignLycpKTtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIHRoZSBwcmVzZW50YXRpb24gd2lkdGguXG4gKi9cbmZ1bmN0aW9uIGdldFByZXNlbnRhdGlvbldpZHRoKCkge1xuICAgIHZhciBhdmFpbGFibGVXaWR0aCA9IFVJVXRpbC5nZXRBdmFpbGFibGVWaWRlb1dpZHRoKCk7XG4gICAgdmFyIGF2YWlsYWJsZUhlaWdodCA9IGdldFByZXNlbnRhdGlvbkhlaWhndCgpO1xuXG4gICAgdmFyIGFzcGVjdFJhdGlvID0gMTYuMCAvIDkuMDtcbiAgICBpZiAoYXZhaWxhYmxlSGVpZ2h0IDwgYXZhaWxhYmxlV2lkdGggLyBhc3BlY3RSYXRpbykge1xuICAgICAgICBhdmFpbGFibGVXaWR0aCA9IE1hdGguZmxvb3IoYXZhaWxhYmxlSGVpZ2h0ICogYXNwZWN0UmF0aW8pO1xuICAgIH1cbiAgICByZXR1cm4gYXZhaWxhYmxlV2lkdGg7XG59XG5cbi8qKlxuICogUmV0dXJucyB0aGUgcHJlc2VudGF0aW9uIGhlaWdodC5cbiAqL1xuZnVuY3Rpb24gZ2V0UHJlc2VudGF0aW9uSGVpaGd0KCkge1xuICAgIHZhciByZW1vdGVWaWRlb3MgPSAkKCcjcmVtb3RlVmlkZW9zJyk7XG4gICAgcmV0dXJuIHdpbmRvdy5pbm5lckhlaWdodCAtIHJlbW90ZVZpZGVvcy5vdXRlckhlaWdodCgpO1xufVxuXG4vKipcbiAqIFJlc2l6ZXMgdGhlIHByZXNlbnRhdGlvbiBpZnJhbWUuXG4gKi9cbmZ1bmN0aW9uIHJlc2l6ZSgpIHtcbiAgICBpZiAoJCgnI3ByZXNlbnRhdGlvbj5pZnJhbWUnKSkge1xuICAgICAgICAkKCcjcHJlc2VudGF0aW9uPmlmcmFtZScpLndpZHRoKGdldFByZXNlbnRhdGlvbldpZHRoKCkpO1xuICAgICAgICAkKCcjcHJlc2VudGF0aW9uPmlmcmFtZScpLmhlaWdodChnZXRQcmVzZW50YXRpb25IZWloZ3QoKSk7XG4gICAgfVxufVxuXG4vKipcbiAqIFNob3dzL2hpZGVzIGEgcHJlc2VudGF0aW9uLlxuICovXG5mdW5jdGlvbiBzZXRQcmVzZW50YXRpb25WaXNpYmxlKHZpc2libGUpIHtcbiAgICB2YXIgcHJlemkgPSAkKCcjcHJlc2VudGF0aW9uPmlmcmFtZScpO1xuICAgIGlmICh2aXNpYmxlKSB7XG4gICAgICAgIC8vIFRyaWdnZXIgdGhlIHZpZGVvLnNlbGVjdGVkIGV2ZW50IHRvIGluZGljYXRlIGEgY2hhbmdlIGluIHRoZVxuICAgICAgICAvLyBsYXJnZSB2aWRlby5cbiAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcihcInZpZGVvLnNlbGVjdGVkXCIsIFt0cnVlXSk7XG5cbiAgICAgICAgJCgnI2xhcmdlVmlkZW8nKS5mYWRlT3V0KDMwMCk7XG4gICAgICAgIHByZXppLmZhZGVJbigzMDAsIGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgcHJlemkuY3NzKHtvcGFjaXR5OicxJ30pO1xuICAgICAgICAgICAgVG9vbGJhclRvZ2dsZXIuZG9ja1Rvb2xiYXIodHJ1ZSk7XG4gICAgICAgICAgICBWaWRlb0xheW91dC5zZXRMYXJnZVZpZGVvVmlzaWJsZShmYWxzZSk7XG4gICAgICAgIH0pO1xuICAgICAgICAkKCcjYWN0aXZlU3BlYWtlcicpLmNzcygndmlzaWJpbGl0eScsICdoaWRkZW4nKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIGlmIChwcmV6aS5jc3MoJ29wYWNpdHknKSA9PSAnMScpIHtcbiAgICAgICAgICAgIHByZXppLmZhZGVPdXQoMzAwLCBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgcHJlemkuY3NzKHtvcGFjaXR5OicwJ30pO1xuICAgICAgICAgICAgICAgICQoJyNyZWxvYWRQcmVzZW50YXRpb24nKS5jc3Moe2Rpc3BsYXk6J25vbmUnfSk7XG4gICAgICAgICAgICAgICAgJCgnI2xhcmdlVmlkZW8nKS5mYWRlSW4oMzAwLCBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgICAgICAgICAgVmlkZW9MYXlvdXQuc2V0TGFyZ2VWaWRlb1Zpc2libGUodHJ1ZSk7XG4gICAgICAgICAgICAgICAgICAgIFRvb2xiYXJUb2dnbGVyLmRvY2tUb29sYmFyKGZhbHNlKTtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgfVxufVxuXG4vKipcbiAqIFByZXNlbnRhdGlvbiBoYXMgYmVlbiByZW1vdmVkLlxuICovXG4kKGRvY3VtZW50KS5iaW5kKCdwcmVzZW50YXRpb25yZW1vdmVkLm11YycsIHByZXNlbnRhdGlvblJlbW92ZWQpO1xuXG4vKipcbiAqIFByZXNlbnRhdGlvbiBoYXMgYmVlbiBhZGRlZC5cbiAqL1xuJChkb2N1bWVudCkuYmluZCgncHJlc2VudGF0aW9uYWRkZWQubXVjJywgcHJlc2VudGF0aW9uQWRkZWQpO1xuXG4vKlxuICogSW5kaWNhdGVzIHByZXNlbnRhdGlvbiBzbGlkZSBjaGFuZ2UuXG4gKi9cbiQoZG9jdW1lbnQpLmJpbmQoJ2dvdG9zbGlkZS5tdWMnLCBmdW5jdGlvbiAoZXZlbnQsIGppZCwgcHJlc1VybCwgY3VycmVudCkge1xuICAgIGlmIChwcmV6aVBsYXllciAmJiBwcmV6aVBsYXllci5nZXRDdXJyZW50U3RlcCgpICE9IGN1cnJlbnQpIHtcbiAgICAgICAgcHJlemlQbGF5ZXIuZmx5VG9TdGVwKGN1cnJlbnQpO1xuXG4gICAgICAgIHZhciBhbmltYXRpb25TdGVwc0FycmF5ID0gcHJlemlQbGF5ZXIuZ2V0QW5pbWF0aW9uQ291bnRPblN0ZXBzKCk7XG4gICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgcGFyc2VJbnQoYW5pbWF0aW9uU3RlcHNBcnJheVtjdXJyZW50XSk7IGkrKykge1xuICAgICAgICAgICAgcHJlemlQbGF5ZXIuZmx5VG9TdGVwKGN1cnJlbnQsIGkpO1xuICAgICAgICB9XG4gICAgfVxufSk7XG5cbi8qKlxuICogT24gdmlkZW8gc2VsZWN0ZWQgZXZlbnQuXG4gKi9cbiQoZG9jdW1lbnQpLmJpbmQoJ3ZpZGVvLnNlbGVjdGVkJywgZnVuY3Rpb24gKGV2ZW50LCBpc1ByZXNlbnRhdGlvbikge1xuICAgIGlmICghaXNQcmVzZW50YXRpb24gJiYgJCgnI3ByZXNlbnRhdGlvbj5pZnJhbWUnKSkge1xuICAgICAgICBzZXRQcmVzZW50YXRpb25WaXNpYmxlKGZhbHNlKTtcbiAgICB9XG59KTtcblxuJCh3aW5kb3cpLnJlc2l6ZShmdW5jdGlvbiAoKSB7XG4gICAgcmVzaXplKCk7XG59KTtcblxubW9kdWxlLmV4cG9ydHMgPSBQcmV6aTtcbiIsInZhciBDaGF0ID0gcmVxdWlyZShcIi4vY2hhdC9DaGF0XCIpO1xudmFyIENvbnRhY3RMaXN0ID0gcmVxdWlyZShcIi4vY29udGFjdGxpc3QvQ29udGFjdExpc3RcIik7XG52YXIgU2V0dGluZ3MgPSByZXF1aXJlKFwiLi9zZXR0aW5ncy9TZXR0aW5nc1wiKTtcbnZhciBTZXR0aW5nc01lbnUgPSByZXF1aXJlKFwiLi9zZXR0aW5ncy9TZXR0aW5nc01lbnVcIik7XG52YXIgVmlkZW9MYXlvdXQgPSByZXF1aXJlKFwiLi4vdmlkZW9sYXlvdXQvVmlkZW9MYXlvdXRcIik7XG52YXIgVG9vbGJhclRvZ2dsZXIgPSByZXF1aXJlKFwiLi4vdG9vbGJhcnMvVG9vbGJhclRvZ2dsZXJcIik7XG52YXIgVUlVdGlsID0gcmVxdWlyZShcIi4uL3V0aWwvVUlVdGlsXCIpO1xuXG4vKipcbiAqIFRvZ2dsZXIgZm9yIHRoZSBjaGF0LCBjb250YWN0IGxpc3QsIHNldHRpbmdzIG1lbnUsIGV0Yy4uXG4gKi9cbnZhciBQYW5lbFRvZ2dsZXIgPSAoZnVuY3Rpb24obXkpIHtcblxuICAgIHZhciBjdXJyZW50bHlPcGVuID0gbnVsbDtcbiAgICB2YXIgYnV0dG9ucyA9IHtcbiAgICAgICAgJyNjaGF0c3BhY2UnOiAnI2NoYXRCb3R0b21CdXR0b24nLFxuICAgICAgICAnI2NvbnRhY3RsaXN0JzogJyNjb250YWN0TGlzdEJ1dHRvbicsXG4gICAgICAgICcjc2V0dGluZ3NtZW51JzogJyNzZXR0aW5nc0J1dHRvbidcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogUmVzaXplcyB0aGUgdmlkZW8gYXJlYVxuICAgICAqIEBwYXJhbSBpc0Nsb3Npbmcgd2hldGhlciB0aGUgc2lkZSBwYW5lbCBpcyBnb2luZyB0byBiZSBjbG9zZWQgb3IgaXMgZ29pbmcgdG8gb3BlbiAvIHJlbWFpbiBvcGVuZWRcbiAgICAgKiBAcGFyYW0gY29tcGxldGVGdW5jdGlvbiBhIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCB3aGVuIHRoZSB2aWRlbyBzcGFjZSBpcyByZXNpemVkXG4gICAgICovXG4gICAgdmFyIHJlc2l6ZVZpZGVvQXJlYSA9IGZ1bmN0aW9uKGlzQ2xvc2luZywgY29tcGxldGVGdW5jdGlvbikge1xuICAgICAgICB2YXIgdmlkZW9zcGFjZSA9ICQoJyN2aWRlb3NwYWNlJyk7XG5cbiAgICAgICAgdmFyIHBhbmVsU2l6ZSA9IGlzQ2xvc2luZyA/IFswLCAwXSA6IFBhbmVsVG9nZ2xlci5nZXRQYW5lbFNpemUoKTtcbiAgICAgICAgdmFyIHZpZGVvc3BhY2VXaWR0aCA9IHdpbmRvdy5pbm5lcldpZHRoIC0gcGFuZWxTaXplWzBdO1xuICAgICAgICB2YXIgdmlkZW9zcGFjZUhlaWdodCA9IHdpbmRvdy5pbm5lckhlaWdodDtcbiAgICAgICAgdmFyIHZpZGVvU2l6ZVxuICAgICAgICAgICAgPSBWaWRlb0xheW91dC5nZXRWaWRlb1NpemUobnVsbCwgbnVsbCwgdmlkZW9zcGFjZVdpZHRoLCB2aWRlb3NwYWNlSGVpZ2h0KTtcbiAgICAgICAgdmFyIHZpZGVvV2lkdGggPSB2aWRlb1NpemVbMF07XG4gICAgICAgIHZhciB2aWRlb0hlaWdodCA9IHZpZGVvU2l6ZVsxXTtcbiAgICAgICAgdmFyIHZpZGVvUG9zaXRpb24gPSBWaWRlb0xheW91dC5nZXRWaWRlb1Bvc2l0aW9uKHZpZGVvV2lkdGgsXG4gICAgICAgICAgICB2aWRlb0hlaWdodCxcbiAgICAgICAgICAgIHZpZGVvc3BhY2VXaWR0aCxcbiAgICAgICAgICAgIHZpZGVvc3BhY2VIZWlnaHQpO1xuICAgICAgICB2YXIgaG9yaXpvbnRhbEluZGVudCA9IHZpZGVvUG9zaXRpb25bMF07XG4gICAgICAgIHZhciB2ZXJ0aWNhbEluZGVudCA9IHZpZGVvUG9zaXRpb25bMV07XG5cbiAgICAgICAgdmFyIHRodW1ibmFpbFNpemUgPSBWaWRlb0xheW91dC5jYWxjdWxhdGVUaHVtYm5haWxTaXplKHZpZGVvc3BhY2VXaWR0aCk7XG4gICAgICAgIHZhciB0aHVtYm5haWxzV2lkdGggPSB0aHVtYm5haWxTaXplWzBdO1xuICAgICAgICB2YXIgdGh1bWJuYWlsc0hlaWdodCA9IHRodW1ibmFpbFNpemVbMV07XG4gICAgICAgIC8vZm9yIGNoYXRcblxuICAgICAgICB2aWRlb3NwYWNlLmFuaW1hdGUoe1xuICAgICAgICAgICAgICAgIHJpZ2h0OiBwYW5lbFNpemVbMF0sXG4gICAgICAgICAgICAgICAgd2lkdGg6IHZpZGVvc3BhY2VXaWR0aCxcbiAgICAgICAgICAgICAgICBoZWlnaHQ6IHZpZGVvc3BhY2VIZWlnaHRcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgcXVldWU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGR1cmF0aW9uOiA1MDAsXG4gICAgICAgICAgICAgICAgY29tcGxldGU6IGNvbXBsZXRlRnVuY3Rpb25cbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICQoJyNyZW1vdGVWaWRlb3MnKS5hbmltYXRlKHtcbiAgICAgICAgICAgICAgICBoZWlnaHQ6IHRodW1ibmFpbHNIZWlnaHRcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgcXVldWU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGR1cmF0aW9uOiA1MDBcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICQoJyNyZW1vdGVWaWRlb3M+c3BhbicpLmFuaW1hdGUoe1xuICAgICAgICAgICAgICAgIGhlaWdodDogdGh1bWJuYWlsc0hlaWdodCxcbiAgICAgICAgICAgICAgICB3aWR0aDogdGh1bWJuYWlsc1dpZHRoXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHF1ZXVlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBkdXJhdGlvbjogNTAwLFxuICAgICAgICAgICAgICAgIGNvbXBsZXRlOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgICAgICQoZG9jdW1lbnQpLnRyaWdnZXIoXG4gICAgICAgICAgICAgICAgICAgICAgICBcInJlbW90ZXZpZGVvLnJlc2l6ZWRcIixcbiAgICAgICAgICAgICAgICAgICAgICAgIFt0aHVtYm5haWxzV2lkdGgsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdGh1bWJuYWlsc0hlaWdodF0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICQoJyNsYXJnZVZpZGVvQ29udGFpbmVyJykuYW5pbWF0ZSh7XG4gICAgICAgICAgICAgICAgd2lkdGg6IHZpZGVvc3BhY2VXaWR0aCxcbiAgICAgICAgICAgICAgICBoZWlnaHQ6IHZpZGVvc3BhY2VIZWlnaHRcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgcXVldWU6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGR1cmF0aW9uOiA1MDBcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICQoJyNsYXJnZVZpZGVvJykuYW5pbWF0ZSh7XG4gICAgICAgICAgICAgICAgd2lkdGg6IHZpZGVvV2lkdGgsXG4gICAgICAgICAgICAgICAgaGVpZ2h0OiB2aWRlb0hlaWdodCxcbiAgICAgICAgICAgICAgICB0b3A6IHZlcnRpY2FsSW5kZW50LFxuICAgICAgICAgICAgICAgIGJvdHRvbTogdmVydGljYWxJbmRlbnQsXG4gICAgICAgICAgICAgICAgbGVmdDogaG9yaXpvbnRhbEluZGVudCxcbiAgICAgICAgICAgICAgICByaWdodDogaG9yaXpvbnRhbEluZGVudFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBxdWV1ZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgZHVyYXRpb246IDUwMFxuICAgICAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIFRvZ2dsZXMgdGhlIHdpbmRvd3MgaW4gdGhlIHNpZGUgcGFuZWxcbiAgICAgKiBAcGFyYW0gb2JqZWN0IHRoZSB3aW5kb3cgdGhhdCBzaG91bGQgYmUgc2hvd25cbiAgICAgKiBAcGFyYW0gc2VsZWN0b3IgdGhlIHNlbGVjdG9yIGZvciB0aGUgZWxlbWVudCBjb250YWluaW5nIHRoZSBwYW5lbFxuICAgICAqIEBwYXJhbSBvbk9wZW5Db21wbGV0ZSBmdW5jdGlvbiB0byBiZSBjYWxsZWQgd2hlbiB0aGUgcGFuZWwgaXMgb3BlbmVkXG4gICAgICogQHBhcmFtIG9uT3BlbiBmdW5jdGlvbiB0byBiZSBjYWxsZWQgaWYgdGhlIHdpbmRvdyBpcyBnb2luZyB0byBiZSBvcGVuZWRcbiAgICAgKiBAcGFyYW0gb25DbG9zZSBmdW5jdGlvbiB0byBiZSBjYWxsZWQgaWYgdGhlIHdpbmRvdyBpcyBnb2luZyB0byBiZSBjbG9zZWRcbiAgICAgKi9cbiAgICB2YXIgdG9nZ2xlID0gZnVuY3Rpb24ob2JqZWN0LCBzZWxlY3Rvciwgb25PcGVuQ29tcGxldGUsIG9uT3Blbiwgb25DbG9zZSkge1xuICAgICAgICBVSVV0aWwuYnV0dG9uQ2xpY2soYnV0dG9uc1tzZWxlY3Rvcl0sIFwiYWN0aXZlXCIpO1xuXG4gICAgICAgIGlmIChvYmplY3QuaXNWaXNpYmxlKCkpIHtcbiAgICAgICAgICAgICQoXCIjdG9hc3QtY29udGFpbmVyXCIpLmFuaW1hdGUoe1xuICAgICAgICAgICAgICAgICAgICByaWdodDogJzVweCdcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgcXVldWU6IGZhbHNlLFxuICAgICAgICAgICAgICAgICAgICBkdXJhdGlvbjogNTAwXG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAkKHNlbGVjdG9yKS5oaWRlKFwic2xpZGVcIiwge1xuICAgICAgICAgICAgICAgIGRpcmVjdGlvbjogXCJyaWdodFwiLFxuICAgICAgICAgICAgICAgIHF1ZXVlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBkdXJhdGlvbjogNTAwXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGlmKHR5cGVvZiBvbkNsb3NlID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgICAgICAgICBvbkNsb3NlKCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGN1cnJlbnRseU9wZW4gPSBudWxsO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgLy8gVW5kb2NrIHRoZSB0b29sYmFyIHdoZW4gdGhlIGNoYXQgaXMgc2hvd24gYW5kIGlmIHdlJ3JlIGluIGFcbiAgICAgICAgICAgIC8vIHZpZGVvIG1vZGUuXG4gICAgICAgICAgICBpZiAoVmlkZW9MYXlvdXQuaXNMYXJnZVZpZGVvVmlzaWJsZSgpKSB7XG4gICAgICAgICAgICAgICAgVG9vbGJhclRvZ2dsZXIuZG9ja1Rvb2xiYXIoZmFsc2UpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZihjdXJyZW50bHlPcGVuKSB7XG4gICAgICAgICAgICAgICAgdmFyIGN1cnJlbnQgPSAkKGN1cnJlbnRseU9wZW4pO1xuICAgICAgICAgICAgICAgIFVJVXRpbC5idXR0b25DbGljayhidXR0b25zW2N1cnJlbnRseU9wZW5dLCBcImFjdGl2ZVwiKTtcbiAgICAgICAgICAgICAgICBjdXJyZW50LmNzcygnei1pbmRleCcsIDQpO1xuICAgICAgICAgICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgICAgICBjdXJyZW50LmNzcygnZGlzcGxheScsICdub25lJyk7XG4gICAgICAgICAgICAgICAgICAgIGN1cnJlbnQuY3NzKCd6LWluZGV4JywgNSk7XG4gICAgICAgICAgICAgICAgfSwgNTAwKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgJChcIiN0b2FzdC1jb250YWluZXJcIikuYW5pbWF0ZSh7XG4gICAgICAgICAgICAgICAgICAgIHJpZ2h0OiAoUGFuZWxUb2dnbGVyLmdldFBhbmVsU2l6ZSgpWzBdICsgNSkgKyAncHgnXG4gICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgIHF1ZXVlOiBmYWxzZSxcbiAgICAgICAgICAgICAgICAgICAgZHVyYXRpb246IDUwMFxuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgJChzZWxlY3Rvcikuc2hvdyhcInNsaWRlXCIsIHtcbiAgICAgICAgICAgICAgICBkaXJlY3Rpb246IFwicmlnaHRcIixcbiAgICAgICAgICAgICAgICBxdWV1ZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgZHVyYXRpb246IDUwMCxcbiAgICAgICAgICAgICAgICBjb21wbGV0ZTogb25PcGVuQ29tcGxldGVcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgaWYodHlwZW9mIG9uT3BlbiA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICAgICAgb25PcGVuKCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGN1cnJlbnRseU9wZW4gPSBzZWxlY3RvcjtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBPcGVucyAvIGNsb3NlcyB0aGUgY2hhdCBhcmVhLlxuICAgICAqL1xuICAgIG15LnRvZ2dsZUNoYXQgPSBmdW5jdGlvbigpIHtcbiAgICAgICAgdmFyIGNoYXRDb21wbGV0ZUZ1bmN0aW9uID0gQ2hhdC5pc1Zpc2libGUoKSA/XG4gICAgICAgICAgICBmdW5jdGlvbigpIHt9IDogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgQ2hhdC5zY3JvbGxDaGF0VG9Cb3R0b20oKTtcbiAgICAgICAgICAgICQoJyNjaGF0c3BhY2UnKS50cmlnZ2VyKCdzaG93bicpO1xuICAgICAgICB9O1xuXG4gICAgICAgIHJlc2l6ZVZpZGVvQXJlYShDaGF0LmlzVmlzaWJsZSgpLCBjaGF0Q29tcGxldGVGdW5jdGlvbik7XG5cbiAgICAgICAgdG9nZ2xlKENoYXQsXG4gICAgICAgICAgICAnI2NoYXRzcGFjZScsXG4gICAgICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgLy8gUmVxdWVzdCB0aGUgZm9jdXMgaW4gdGhlIG5pY2tuYW1lIGZpZWxkIG9yIHRoZSBjaGF0IGlucHV0IGZpZWxkLlxuICAgICAgICAgICAgICAgIGlmICgkKCcjbmlja25hbWUnKS5jc3MoJ3Zpc2liaWxpdHknKSA9PT0gJ3Zpc2libGUnKSB7XG4gICAgICAgICAgICAgICAgICAgICQoJyNuaWNraW5wdXQnKS5mb2N1cygpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICQoJyN1c2VybXNnJykuZm9jdXMoKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgbnVsbCxcbiAgICAgICAgICAgIENoYXQucmVzaXplQ2hhdCxcbiAgICAgICAgICAgIG51bGwpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBPcGVucyAvIGNsb3NlcyB0aGUgY29udGFjdCBsaXN0IGFyZWEuXG4gICAgICovXG4gICAgbXkudG9nZ2xlQ29udGFjdExpc3QgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBjb21wbGV0ZUZ1bmN0aW9uID0gQ29udGFjdExpc3QuaXNWaXNpYmxlKCkgP1xuICAgICAgICAgICAgZnVuY3Rpb24oKSB7fSA6IGZ1bmN0aW9uICgpIHsgJCgnI2NvbnRhY3RsaXN0JykudHJpZ2dlcignc2hvd24nKTt9O1xuICAgICAgICByZXNpemVWaWRlb0FyZWEoQ29udGFjdExpc3QuaXNWaXNpYmxlKCksIGNvbXBsZXRlRnVuY3Rpb24pO1xuXG4gICAgICAgIHRvZ2dsZShDb250YWN0TGlzdCxcbiAgICAgICAgICAgICcjY29udGFjdGxpc3QnLFxuICAgICAgICAgICAgbnVsbCxcbiAgICAgICAgICAgIGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgICAgIENvbnRhY3RMaXN0LnNldFZpc3VhbE5vdGlmaWNhdGlvbihmYWxzZSk7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgbnVsbCk7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIE9wZW5zIC8gY2xvc2VzIHRoZSBzZXR0aW5ncyBtZW51XG4gICAgICovXG4gICAgbXkudG9nZ2xlU2V0dGluZ3NNZW51ID0gZnVuY3Rpb24oKSB7XG4gICAgICAgIHJlc2l6ZVZpZGVvQXJlYShTZXR0aW5nc01lbnUuaXNWaXNpYmxlKCksIGZ1bmN0aW9uICgpe30pO1xuICAgICAgICB0b2dnbGUoU2V0dGluZ3NNZW51LFxuICAgICAgICAgICAgJyNzZXR0aW5nc21lbnUnLFxuICAgICAgICAgICAgbnVsbCxcbiAgICAgICAgICAgIGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgICAgIHZhciBzZXR0aW5ncyA9IFNldHRpbmdzLmdldFNldHRpbmdzKCk7XG4gICAgICAgICAgICAgICAgJCgnI3NldERpc3BsYXlOYW1lJykuZ2V0KDApLnZhbHVlID0gc2V0dGluZ3MuZGlzcGxheU5hbWU7XG4gICAgICAgICAgICAgICAgJCgnI3NldEVtYWlsJykuZ2V0KDApLnZhbHVlID0gc2V0dGluZ3MuZW1haWw7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgbnVsbCk7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIHNpemUgb2YgdGhlIHNpZGUgcGFuZWwuXG4gICAgICovXG4gICAgbXkuZ2V0UGFuZWxTaXplID0gZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgYXZhaWxhYmxlSGVpZ2h0ID0gd2luZG93LmlubmVySGVpZ2h0O1xuICAgICAgICB2YXIgYXZhaWxhYmxlV2lkdGggPSB3aW5kb3cuaW5uZXJXaWR0aDtcblxuICAgICAgICB2YXIgcGFuZWxXaWR0aCA9IDIwMDtcbiAgICAgICAgaWYgKGF2YWlsYWJsZVdpZHRoICogMC4yIDwgMjAwKSB7XG4gICAgICAgICAgICBwYW5lbFdpZHRoID0gYXZhaWxhYmxlV2lkdGggKiAwLjI7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gW3BhbmVsV2lkdGgsIGF2YWlsYWJsZUhlaWdodF07XG4gICAgfTtcblxuICAgIG15LmlzVmlzaWJsZSA9IGZ1bmN0aW9uKCkge1xuICAgICAgICByZXR1cm4gKENoYXQuaXNWaXNpYmxlKCkgfHwgQ29udGFjdExpc3QuaXNWaXNpYmxlKCkgfHwgU2V0dGluZ3NNZW51LmlzVmlzaWJsZSgpKTtcbiAgICB9O1xuXG4gICAgcmV0dXJuIG15O1xuXG59KFBhbmVsVG9nZ2xlciB8fCB7fSkpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IFBhbmVsVG9nZ2xlcjsiLCIvKiBnbG9iYWwgJCwgVXRpbCwgbmlja25hbWU6dHJ1ZSwgc2hvd1Rvb2xiYXIgKi9cbnZhciBSZXBsYWNlbWVudCA9IHJlcXVpcmUoXCIuL1JlcGxhY2VtZW50XCIpO1xudmFyIENvbW1hbmRzUHJvY2Vzc29yID0gcmVxdWlyZShcIi4vQ29tbWFuZHNcIik7XG52YXIgVG9vbGJhclRvZ2dsZXIgPSByZXF1aXJlKFwiLi4vLi4vdG9vbGJhcnMvVG9vbGJhclRvZ2dsZXJcIik7XG52YXIgc21pbGV5cyA9IHJlcXVpcmUoXCIuL3NtaWxleXMuanNvblwiKS5zbWlsZXlzO1xuXG52YXIgbm90aWZpY2F0aW9uSW50ZXJ2YWwgPSBmYWxzZTtcbnZhciB1bnJlYWRNZXNzYWdlcyA9IDA7XG5cblxuLyoqXG4gKiBTaG93cy9oaWRlcyBhIHZpc3VhbCBub3RpZmljYXRpb24sIGluZGljYXRpbmcgdGhhdCBhIG1lc3NhZ2UgaGFzIGFycml2ZWQuXG4gKi9cbmZ1bmN0aW9uIHNldFZpc3VhbE5vdGlmaWNhdGlvbihzaG93KSB7XG4gICAgdmFyIHVucmVhZE1zZ0VsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndW5yZWFkTWVzc2FnZXMnKTtcbiAgICB2YXIgdW5yZWFkTXNnQm90dG9tRWxlbWVudFxuICAgICAgICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdib3R0b21VbnJlYWRNZXNzYWdlcycpO1xuXG4gICAgdmFyIGdsb3dlciA9ICQoJyNjaGF0QnV0dG9uJyk7XG4gICAgdmFyIGJvdHRvbUdsb3dlciA9ICQoJyNjaGF0Qm90dG9tQnV0dG9uJyk7XG5cbiAgICBpZiAodW5yZWFkTWVzc2FnZXMpIHtcbiAgICAgICAgdW5yZWFkTXNnRWxlbWVudC5pbm5lckhUTUwgPSB1bnJlYWRNZXNzYWdlcy50b1N0cmluZygpO1xuICAgICAgICB1bnJlYWRNc2dCb3R0b21FbGVtZW50LmlubmVySFRNTCA9IHVucmVhZE1lc3NhZ2VzLnRvU3RyaW5nKCk7XG5cbiAgICAgICAgVG9vbGJhclRvZ2dsZXIuZG9ja1Rvb2xiYXIodHJ1ZSk7XG5cbiAgICAgICAgdmFyIGNoYXRCdXR0b25FbGVtZW50XG4gICAgICAgICAgICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdjaGF0QnV0dG9uJykucGFyZW50Tm9kZTtcbiAgICAgICAgdmFyIGxlZnRJbmRlbnQgPSAoVXRpbC5nZXRUZXh0V2lkdGgoY2hhdEJ1dHRvbkVsZW1lbnQpIC1cbiAgICAgICAgICAgIFV0aWwuZ2V0VGV4dFdpZHRoKHVucmVhZE1zZ0VsZW1lbnQpKSAvIDI7XG4gICAgICAgIHZhciB0b3BJbmRlbnQgPSAoVXRpbC5nZXRUZXh0SGVpZ2h0KGNoYXRCdXR0b25FbGVtZW50KSAtXG4gICAgICAgICAgICBVdGlsLmdldFRleHRIZWlnaHQodW5yZWFkTXNnRWxlbWVudCkpIC8gMiAtIDM7XG5cbiAgICAgICAgdW5yZWFkTXNnRWxlbWVudC5zZXRBdHRyaWJ1dGUoXG4gICAgICAgICAgICAnc3R5bGUnLFxuICAgICAgICAgICAgICAgICd0b3A6JyArIHRvcEluZGVudCArXG4gICAgICAgICAgICAgICAgJzsgbGVmdDonICsgbGVmdEluZGVudCArICc7Jyk7XG5cbiAgICAgICAgdmFyIGNoYXRCb3R0b21CdXR0b25FbGVtZW50XG4gICAgICAgICAgICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdjaGF0Qm90dG9tQnV0dG9uJykucGFyZW50Tm9kZTtcbiAgICAgICAgdmFyIGJvdHRvbUxlZnRJbmRlbnQgPSAoVXRpbC5nZXRUZXh0V2lkdGgoY2hhdEJvdHRvbUJ1dHRvbkVsZW1lbnQpIC1cbiAgICAgICAgICAgIFV0aWwuZ2V0VGV4dFdpZHRoKHVucmVhZE1zZ0JvdHRvbUVsZW1lbnQpKSAvIDI7XG4gICAgICAgIHZhciBib3R0b21Ub3BJbmRlbnQgPSAoVXRpbC5nZXRUZXh0SGVpZ2h0KGNoYXRCb3R0b21CdXR0b25FbGVtZW50KSAtXG4gICAgICAgICAgICBVdGlsLmdldFRleHRIZWlnaHQodW5yZWFkTXNnQm90dG9tRWxlbWVudCkpIC8gMiAtIDI7XG5cbiAgICAgICAgdW5yZWFkTXNnQm90dG9tRWxlbWVudC5zZXRBdHRyaWJ1dGUoXG4gICAgICAgICAgICAnc3R5bGUnLFxuICAgICAgICAgICAgICAgICd0b3A6JyArIGJvdHRvbVRvcEluZGVudCArXG4gICAgICAgICAgICAgICAgJzsgbGVmdDonICsgYm90dG9tTGVmdEluZGVudCArICc7Jyk7XG5cblxuICAgICAgICBpZiAoIWdsb3dlci5oYXNDbGFzcygnaWNvbi1jaGF0LXNpbXBsZScpKSB7XG4gICAgICAgICAgICBnbG93ZXIucmVtb3ZlQ2xhc3MoJ2ljb24tY2hhdCcpO1xuICAgICAgICAgICAgZ2xvd2VyLmFkZENsYXNzKCdpY29uLWNoYXQtc2ltcGxlJyk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIHVucmVhZE1zZ0VsZW1lbnQuaW5uZXJIVE1MID0gJyc7XG4gICAgICAgIHVucmVhZE1zZ0JvdHRvbUVsZW1lbnQuaW5uZXJIVE1MID0gJyc7XG4gICAgICAgIGdsb3dlci5yZW1vdmVDbGFzcygnaWNvbi1jaGF0LXNpbXBsZScpO1xuICAgICAgICBnbG93ZXIuYWRkQ2xhc3MoJ2ljb24tY2hhdCcpO1xuICAgIH1cblxuICAgIGlmIChzaG93ICYmICFub3RpZmljYXRpb25JbnRlcnZhbCkge1xuICAgICAgICBub3RpZmljYXRpb25JbnRlcnZhbCA9IHdpbmRvdy5zZXRJbnRlcnZhbChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBnbG93ZXIudG9nZ2xlQ2xhc3MoJ2FjdGl2ZScpO1xuICAgICAgICAgICAgYm90dG9tR2xvd2VyLnRvZ2dsZUNsYXNzKCdhY3RpdmUgZ2xvd2luZycpO1xuICAgICAgICB9LCA4MDApO1xuICAgIH1cbiAgICBlbHNlIGlmICghc2hvdyAmJiBub3RpZmljYXRpb25JbnRlcnZhbCkge1xuICAgICAgICB3aW5kb3cuY2xlYXJJbnRlcnZhbChub3RpZmljYXRpb25JbnRlcnZhbCk7XG4gICAgICAgIG5vdGlmaWNhdGlvbkludGVydmFsID0gZmFsc2U7XG4gICAgICAgIGdsb3dlci5yZW1vdmVDbGFzcygnYWN0aXZlJyk7XG4gICAgICAgIGJvdHRvbUdsb3dlci5yZW1vdmVDbGFzcygnZ2xvd2luZycpO1xuICAgICAgICBib3R0b21HbG93ZXIuYWRkQ2xhc3MoJ2FjdGl2ZScpO1xuICAgIH1cbn1cblxuXG4vKipcbiAqIFJldHVybnMgdGhlIGN1cnJlbnQgdGltZSBpbiB0aGUgZm9ybWF0IGl0IGlzIHNob3duIHRvIHRoZSB1c2VyXG4gKiBAcmV0dXJucyB7c3RyaW5nfVxuICovXG5mdW5jdGlvbiBnZXRDdXJyZW50VGltZSgpIHtcbiAgICB2YXIgbm93ICAgICA9IG5ldyBEYXRlKCk7XG4gICAgdmFyIGhvdXIgICAgPSBub3cuZ2V0SG91cnMoKTtcbiAgICB2YXIgbWludXRlICA9IG5vdy5nZXRNaW51dGVzKCk7XG4gICAgdmFyIHNlY29uZCAgPSBub3cuZ2V0U2Vjb25kcygpO1xuICAgIGlmKGhvdXIudG9TdHJpbmcoKS5sZW5ndGggPT09IDEpIHtcbiAgICAgICAgaG91ciA9ICcwJytob3VyO1xuICAgIH1cbiAgICBpZihtaW51dGUudG9TdHJpbmcoKS5sZW5ndGggPT09IDEpIHtcbiAgICAgICAgbWludXRlID0gJzAnK21pbnV0ZTtcbiAgICB9XG4gICAgaWYoc2Vjb25kLnRvU3RyaW5nKCkubGVuZ3RoID09PSAxKSB7XG4gICAgICAgIHNlY29uZCA9ICcwJytzZWNvbmQ7XG4gICAgfVxuICAgIHJldHVybiBob3VyKyc6JyttaW51dGUrJzonK3NlY29uZDtcbn1cblxuZnVuY3Rpb24gdG9nZ2xlU21pbGV5cygpXG57XG4gICAgdmFyIHNtaWxleXMgPSAkKCcjc21pbGV5c0NvbnRhaW5lcicpO1xuICAgIGlmKCFzbWlsZXlzLmlzKCc6dmlzaWJsZScpKSB7XG4gICAgICAgIHNtaWxleXMuc2hvdyhcInNsaWRlXCIsIHsgZGlyZWN0aW9uOiBcImRvd25cIiwgZHVyYXRpb246IDMwMH0pO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHNtaWxleXMuaGlkZShcInNsaWRlXCIsIHsgZGlyZWN0aW9uOiBcImRvd25cIiwgZHVyYXRpb246IDMwMH0pO1xuICAgIH1cbiAgICAkKCcjdXNlcm1zZycpLmZvY3VzKCk7XG59XG5cbmZ1bmN0aW9uIGFkZENsaWNrRnVuY3Rpb24oc21pbGV5LCBudW1iZXIpIHtcbiAgICBzbWlsZXkub25jbGljayA9IGZ1bmN0aW9uIGFkZFNtaWxleVRvTWVzc2FnZSgpIHtcbiAgICAgICAgdmFyIHVzZXJtc2cgPSAkKCcjdXNlcm1zZycpO1xuICAgICAgICB2YXIgbWVzc2FnZSA9IHVzZXJtc2cudmFsKCk7XG4gICAgICAgIG1lc3NhZ2UgKz0gc21pbGV5c1snc21pbGV5JyArIG51bWJlcl07XG4gICAgICAgIHVzZXJtc2cudmFsKG1lc3NhZ2UpO1xuICAgICAgICB1c2VybXNnLmdldCgwKS5zZXRTZWxlY3Rpb25SYW5nZShtZXNzYWdlLmxlbmd0aCwgbWVzc2FnZS5sZW5ndGgpO1xuICAgICAgICB0b2dnbGVTbWlsZXlzKCk7XG4gICAgICAgIHVzZXJtc2cuZm9jdXMoKTtcbiAgICB9O1xufVxuXG4vKipcbiAqIEFkZHMgdGhlIHNtaWxleXMgY29udGFpbmVyIHRvIHRoZSBjaGF0XG4gKi9cbmZ1bmN0aW9uIGFkZFNtaWxleXMoKSB7XG4gICAgdmFyIHNtaWxleXNDb250YWluZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTtcbiAgICBzbWlsZXlzQ29udGFpbmVyLmlkID0gJ3NtaWxleXNDb250YWluZXInO1xuICAgIGZvcih2YXIgaSA9IDE7IGkgPD0gMjE7IGkrKykge1xuICAgICAgICB2YXIgc21pbGV5Q29udGFpbmVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gICAgICAgIHNtaWxleUNvbnRhaW5lci5pZCA9ICdzbWlsZXknICsgaTtcbiAgICAgICAgc21pbGV5Q29udGFpbmVyLmNsYXNzTmFtZSA9ICdzbWlsZXlDb250YWluZXInO1xuICAgICAgICB2YXIgc21pbGV5ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnaW1nJyk7XG4gICAgICAgIHNtaWxleS5zcmMgPSAnaW1hZ2VzL3NtaWxleXMvc21pbGV5JyArIGkgKyAnLnN2Zyc7XG4gICAgICAgIHNtaWxleS5jbGFzc05hbWUgPSAgJ3NtaWxleSc7XG4gICAgICAgIGFkZENsaWNrRnVuY3Rpb24oc21pbGV5LCBpKTtcbiAgICAgICAgc21pbGV5Q29udGFpbmVyLmFwcGVuZENoaWxkKHNtaWxleSk7XG4gICAgICAgIHNtaWxleXNDb250YWluZXIuYXBwZW5kQ2hpbGQoc21pbGV5Q29udGFpbmVyKTtcbiAgICB9XG5cbiAgICAkKFwiI2NoYXRzcGFjZVwiKS5hcHBlbmQoc21pbGV5c0NvbnRhaW5lcik7XG59XG5cbi8qKlxuICogUmVzaXplcyB0aGUgY2hhdCBjb252ZXJzYXRpb24uXG4gKi9cbmZ1bmN0aW9uIHJlc2l6ZUNoYXRDb252ZXJzYXRpb24oKSB7XG4gICAgdmFyIG1zZ2FyZWFIZWlnaHQgPSAkKCcjdXNlcm1zZycpLm91dGVySGVpZ2h0KCk7XG4gICAgdmFyIGNoYXRzcGFjZSA9ICQoJyNjaGF0c3BhY2UnKTtcbiAgICB2YXIgd2lkdGggPSBjaGF0c3BhY2Uud2lkdGgoKTtcbiAgICB2YXIgY2hhdCA9ICQoJyNjaGF0Y29udmVyc2F0aW9uJyk7XG4gICAgdmFyIHNtaWxleXMgPSAkKCcjc21pbGV5c2FyZWEnKTtcblxuICAgIHNtaWxleXMuaGVpZ2h0KG1zZ2FyZWFIZWlnaHQpO1xuICAgICQoXCIjc21pbGV5c1wiKS5jc3MoJ2JvdHRvbScsIChtc2dhcmVhSGVpZ2h0IC0gMjYpIC8gMik7XG4gICAgJCgnI3NtaWxleXNDb250YWluZXInKS5jc3MoJ2JvdHRvbScsIG1zZ2FyZWFIZWlnaHQpO1xuICAgIGNoYXQud2lkdGgod2lkdGggLSAxMCk7XG4gICAgY2hhdC5oZWlnaHQod2luZG93LmlubmVySGVpZ2h0IC0gMTUgLSBtc2dhcmVhSGVpZ2h0KTtcbn1cblxuLyoqXG4gKiBDaGF0IHJlbGF0ZWQgdXNlciBpbnRlcmZhY2UuXG4gKi9cbnZhciBDaGF0ID0gKGZ1bmN0aW9uIChteSkge1xuICAgIC8qKlxuICAgICAqIEluaXRpYWxpemVzIGNoYXQgcmVsYXRlZCBpbnRlcmZhY2UuXG4gICAgICovXG4gICAgbXkuaW5pdCA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIHN0b3JlZERpc3BsYXlOYW1lID0gd2luZG93LmxvY2FsU3RvcmFnZS5kaXNwbGF5bmFtZTtcbiAgICAgICAgaWYgKHN0b3JlZERpc3BsYXlOYW1lKSB7XG4gICAgICAgICAgICBuaWNrbmFtZSA9IHN0b3JlZERpc3BsYXlOYW1lO1xuXG4gICAgICAgICAgICBDaGF0LnNldENoYXRDb252ZXJzYXRpb25Nb2RlKHRydWUpO1xuICAgICAgICB9XG5cbiAgICAgICAgJCgnI25pY2tpbnB1dCcpLmtleWRvd24oZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgICAgICBpZiAoZXZlbnQua2V5Q29kZSA9PT0gMTMpIHtcbiAgICAgICAgICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICAgICAgICAgIHZhciB2YWwgPSBVdGlsLmVzY2FwZUh0bWwodGhpcy52YWx1ZSk7XG4gICAgICAgICAgICAgICAgdGhpcy52YWx1ZSA9ICcnO1xuICAgICAgICAgICAgICAgIGlmICghbmlja25hbWUpIHtcbiAgICAgICAgICAgICAgICAgICAgbmlja25hbWUgPSB2YWw7XG4gICAgICAgICAgICAgICAgICAgIHdpbmRvdy5sb2NhbFN0b3JhZ2UuZGlzcGxheW5hbWUgPSBuaWNrbmFtZTtcblxuICAgICAgICAgICAgICAgICAgICB4bXBwLmFkZFRvUHJlc2VuY2UoXCJkaXNwbGF5TmFtZVwiLCBuaWNrbmFtZSk7XG5cbiAgICAgICAgICAgICAgICAgICAgQ2hhdC5zZXRDaGF0Q29udmVyc2F0aW9uTW9kZSh0cnVlKTtcblxuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgICAkKCcjdXNlcm1zZycpLmtleWRvd24oZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgICAgICBpZiAoZXZlbnQua2V5Q29kZSA9PT0gMTMpIHtcbiAgICAgICAgICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICAgICAgICAgIHZhciB2YWx1ZSA9IHRoaXMudmFsdWU7XG4gICAgICAgICAgICAgICAgJCgnI3VzZXJtc2cnKS52YWwoJycpLnRyaWdnZXIoJ2F1dG9zaXplLnJlc2l6ZScpO1xuICAgICAgICAgICAgICAgIHRoaXMuZm9jdXMoKTtcbiAgICAgICAgICAgICAgICB2YXIgY29tbWFuZCA9IG5ldyBDb21tYW5kc1Byb2Nlc3Nvcih2YWx1ZSk7XG4gICAgICAgICAgICAgICAgaWYoY29tbWFuZC5pc0NvbW1hbmQoKSlcbiAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgIGNvbW1hbmQucHJvY2Vzc0NvbW1hbmQoKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgdmFyIG1lc3NhZ2UgPSBVdGlsLmVzY2FwZUh0bWwodmFsdWUpO1xuICAgICAgICAgICAgICAgICAgICB4bXBwLnNlbmRDaGF0TWVzc2FnZShtZXNzYWdlLCBuaWNrbmFtZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgICB2YXIgb25UZXh0QXJlYVJlc2l6ZSA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHJlc2l6ZUNoYXRDb252ZXJzYXRpb24oKTtcbiAgICAgICAgICAgIENoYXQuc2Nyb2xsQ2hhdFRvQm90dG9tKCk7XG4gICAgICAgIH07XG4gICAgICAgICQoJyN1c2VybXNnJykuYXV0b3NpemUoe2NhbGxiYWNrOiBvblRleHRBcmVhUmVzaXplfSk7XG5cbiAgICAgICAgJChcIiNjaGF0c3BhY2VcIikuYmluZChcInNob3duXCIsXG4gICAgICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgdW5yZWFkTWVzc2FnZXMgPSAwO1xuICAgICAgICAgICAgICAgIHNldFZpc3VhbE5vdGlmaWNhdGlvbihmYWxzZSk7XG4gICAgICAgICAgICB9KTtcblxuICAgICAgICBhZGRTbWlsZXlzKCk7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIEFwcGVuZHMgdGhlIGdpdmVuIG1lc3NhZ2UgdG8gdGhlIGNoYXQgY29udmVyc2F0aW9uLlxuICAgICAqL1xuICAgIG15LnVwZGF0ZUNoYXRDb252ZXJzYXRpb24gPSBmdW5jdGlvbiAoZnJvbSwgZGlzcGxheU5hbWUsIG1lc3NhZ2UpIHtcbiAgICAgICAgdmFyIGRpdkNsYXNzTmFtZSA9ICcnO1xuXG4gICAgICAgIGlmICh4bXBwLm15SmlkKCkgPT09IGZyb20pIHtcbiAgICAgICAgICAgIGRpdkNsYXNzTmFtZSA9IFwibG9jYWx1c2VyXCI7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBkaXZDbGFzc05hbWUgPSBcInJlbW90ZXVzZXJcIjtcblxuICAgICAgICAgICAgaWYgKCFDaGF0LmlzVmlzaWJsZSgpKSB7XG4gICAgICAgICAgICAgICAgdW5yZWFkTWVzc2FnZXMrKztcbiAgICAgICAgICAgICAgICBVdGlsLnBsYXlTb3VuZE5vdGlmaWNhdGlvbignY2hhdE5vdGlmaWNhdGlvbicpO1xuICAgICAgICAgICAgICAgIHNldFZpc3VhbE5vdGlmaWNhdGlvbih0cnVlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHJlcGxhY2UgbGlua3MgYW5kIHNtaWxleXNcbiAgICAgICAgLy8gU3Ryb3BoZSBhbHJlYWR5IGVzY2FwZXMgc3BlY2lhbCBzeW1ib2xzIG9uIHNlbmRpbmcsXG4gICAgICAgIC8vIHNvIHdlIGVzY2FwZSBoZXJlIG9ubHkgdGFncyB0byBhdm9pZCBkb3VibGUgJmFtcDtcbiAgICAgICAgdmFyIGVzY01lc3NhZ2UgPSBtZXNzYWdlLnJlcGxhY2UoLzwvZywgJyZsdDsnKS5cbiAgICAgICAgICAgIHJlcGxhY2UoLz4vZywgJyZndDsnKS5yZXBsYWNlKC9cXG4vZywgJzxici8+Jyk7XG4gICAgICAgIHZhciBlc2NEaXNwbGF5TmFtZSA9IFV0aWwuZXNjYXBlSHRtbChkaXNwbGF5TmFtZSk7XG4gICAgICAgIG1lc3NhZ2UgPSBSZXBsYWNlbWVudC5wcm9jZXNzUmVwbGFjZW1lbnRzKGVzY01lc3NhZ2UpO1xuXG4gICAgICAgIHZhciBtZXNzYWdlQ29udGFpbmVyID1cbiAgICAgICAgICAgICc8ZGl2IGNsYXNzPVwiY2hhdG1lc3NhZ2VcIj4nK1xuICAgICAgICAgICAgICAgICc8aW1nIHNyYz1cIi4uL2ltYWdlcy9jaGF0QXJyb3cuc3ZnXCIgY2xhc3M9XCJjaGF0QXJyb3dcIj4nICtcbiAgICAgICAgICAgICAgICAnPGRpdiBjbGFzcz1cInVzZXJuYW1lICcgKyBkaXZDbGFzc05hbWUgKydcIj4nICsgZXNjRGlzcGxheU5hbWUgK1xuICAgICAgICAgICAgICAgICc8L2Rpdj4nICsgJzxkaXYgY2xhc3M9XCJ0aW1lc3RhbXBcIj4nICsgZ2V0Q3VycmVudFRpbWUoKSArXG4gICAgICAgICAgICAgICAgJzwvZGl2PicgKyAnPGRpdiBjbGFzcz1cInVzZXJtZXNzYWdlXCI+JyArIG1lc3NhZ2UgKyAnPC9kaXY+JyArXG4gICAgICAgICAgICAnPC9kaXY+JztcblxuICAgICAgICAkKCcjY2hhdGNvbnZlcnNhdGlvbicpLmFwcGVuZChtZXNzYWdlQ29udGFpbmVyKTtcbiAgICAgICAgJCgnI2NoYXRjb252ZXJzYXRpb24nKS5hbmltYXRlKFxuICAgICAgICAgICAgICAgIHsgc2Nyb2xsVG9wOiAkKCcjY2hhdGNvbnZlcnNhdGlvbicpWzBdLnNjcm9sbEhlaWdodH0sIDEwMDApO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBBcHBlbmRzIGVycm9yIG1lc3NhZ2UgdG8gdGhlIGNvbnZlcnNhdGlvblxuICAgICAqIEBwYXJhbSBlcnJvck1lc3NhZ2UgdGhlIHJlY2VpdmVkIGVycm9yIG1lc3NhZ2UuXG4gICAgICogQHBhcmFtIG9yaWdpbmFsVGV4dCB0aGUgb3JpZ2luYWwgbWVzc2FnZS5cbiAgICAgKi9cbiAgICBteS5jaGF0QWRkRXJyb3IgPSBmdW5jdGlvbihlcnJvck1lc3NhZ2UsIG9yaWdpbmFsVGV4dClcbiAgICB7XG4gICAgICAgIGVycm9yTWVzc2FnZSA9IFV0aWwuZXNjYXBlSHRtbChlcnJvck1lc3NhZ2UpO1xuICAgICAgICBvcmlnaW5hbFRleHQgPSBVdGlsLmVzY2FwZUh0bWwob3JpZ2luYWxUZXh0KTtcblxuICAgICAgICAkKCcjY2hhdGNvbnZlcnNhdGlvbicpLmFwcGVuZChcbiAgICAgICAgICAgICc8ZGl2IGNsYXNzPVwiZXJyb3JNZXNzYWdlXCI+PGI+RXJyb3I6IDwvYj4nICsgJ1lvdXIgbWVzc2FnZScgK1xuICAgICAgICAgICAgKG9yaWdpbmFsVGV4dD8gKCcgXFxcIicrIG9yaWdpbmFsVGV4dCArICdcXFwiJykgOiBcIlwiKSArXG4gICAgICAgICAgICAnIHdhcyBub3Qgc2VudC4nICtcbiAgICAgICAgICAgIChlcnJvck1lc3NhZ2U/ICgnIFJlYXNvbjogJyArIGVycm9yTWVzc2FnZSkgOiAnJykgKyAgJzwvZGl2PicpO1xuICAgICAgICAkKCcjY2hhdGNvbnZlcnNhdGlvbicpLmFuaW1hdGUoXG4gICAgICAgICAgICB7IHNjcm9sbFRvcDogJCgnI2NoYXRjb252ZXJzYXRpb24nKVswXS5zY3JvbGxIZWlnaHR9LCAxMDAwKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogU2V0cyB0aGUgc3ViamVjdCB0byB0aGUgVUlcbiAgICAgKiBAcGFyYW0gc3ViamVjdCB0aGUgc3ViamVjdFxuICAgICAqL1xuICAgIG15LmNoYXRTZXRTdWJqZWN0ID0gZnVuY3Rpb24oc3ViamVjdClcbiAgICB7XG4gICAgICAgIGlmKHN1YmplY3QpXG4gICAgICAgICAgICBzdWJqZWN0ID0gc3ViamVjdC50cmltKCk7XG4gICAgICAgICQoJyNzdWJqZWN0JykuaHRtbChSZXBsYWNlbWVudC5saW5raWZ5KFV0aWwuZXNjYXBlSHRtbChzdWJqZWN0KSkpO1xuICAgICAgICBpZihzdWJqZWN0ID09PSBcIlwiKVxuICAgICAgICB7XG4gICAgICAgICAgICAkKFwiI3N1YmplY3RcIikuY3NzKHtkaXNwbGF5OiBcIm5vbmVcIn0pO1xuICAgICAgICB9XG4gICAgICAgIGVsc2VcbiAgICAgICAge1xuICAgICAgICAgICAgJChcIiNzdWJqZWN0XCIpLmNzcyh7ZGlzcGxheTogXCJibG9ja1wifSk7XG4gICAgICAgIH1cbiAgICB9O1xuXG5cblxuICAgIC8qKlxuICAgICAqIFNldHMgdGhlIGNoYXQgY29udmVyc2F0aW9uIG1vZGUuXG4gICAgICovXG4gICAgbXkuc2V0Q2hhdENvbnZlcnNhdGlvbk1vZGUgPSBmdW5jdGlvbiAoaXNDb252ZXJzYXRpb25Nb2RlKSB7XG4gICAgICAgIGlmIChpc0NvbnZlcnNhdGlvbk1vZGUpIHtcbiAgICAgICAgICAgICQoJyNuaWNrbmFtZScpLmNzcyh7dmlzaWJpbGl0eTogJ2hpZGRlbid9KTtcbiAgICAgICAgICAgICQoJyNjaGF0Y29udmVyc2F0aW9uJykuY3NzKHt2aXNpYmlsaXR5OiAndmlzaWJsZSd9KTtcbiAgICAgICAgICAgICQoJyN1c2VybXNnJykuY3NzKHt2aXNpYmlsaXR5OiAndmlzaWJsZSd9KTtcbiAgICAgICAgICAgICQoJyNzbWlsZXlzYXJlYScpLmNzcyh7dmlzaWJpbGl0eTogJ3Zpc2libGUnfSk7XG4gICAgICAgICAgICAkKCcjdXNlcm1zZycpLmZvY3VzKCk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogUmVzaXplcyB0aGUgY2hhdCBhcmVhLlxuICAgICAqL1xuICAgIG15LnJlc2l6ZUNoYXQgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBjaGF0U2l6ZSA9IHJlcXVpcmUoXCIuLi9TaWRlUGFuZWxUb2dnbGVyXCIpLmdldFBhbmVsU2l6ZSgpO1xuXG4gICAgICAgICQoJyNjaGF0c3BhY2UnKS53aWR0aChjaGF0U2l6ZVswXSk7XG4gICAgICAgICQoJyNjaGF0c3BhY2UnKS5oZWlnaHQoY2hhdFNpemVbMV0pO1xuXG4gICAgICAgIHJlc2l6ZUNoYXRDb252ZXJzYXRpb24oKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogSW5kaWNhdGVzIGlmIHRoZSBjaGF0IGlzIGN1cnJlbnRseSB2aXNpYmxlLlxuICAgICAqL1xuICAgIG15LmlzVmlzaWJsZSA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuICQoJyNjaGF0c3BhY2UnKS5pcyhcIjp2aXNpYmxlXCIpO1xuICAgIH07XG4gICAgLyoqXG4gICAgICogU2hvd3MgYW5kIGhpZGVzIHRoZSB3aW5kb3cgd2l0aCB0aGUgc21pbGV5c1xuICAgICAqL1xuICAgIG15LnRvZ2dsZVNtaWxleXMgPSB0b2dnbGVTbWlsZXlzO1xuXG4gICAgLyoqXG4gICAgICogU2Nyb2xscyBjaGF0IHRvIHRoZSBib3R0b20uXG4gICAgICovXG4gICAgbXkuc2Nyb2xsQ2hhdFRvQm90dG9tID0gZnVuY3Rpb24oKSB7XG4gICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgJCgnI2NoYXRjb252ZXJzYXRpb24nKS5zY3JvbGxUb3AoXG4gICAgICAgICAgICAgICAgJCgnI2NoYXRjb252ZXJzYXRpb24nKVswXS5zY3JvbGxIZWlnaHQpO1xuICAgICAgICB9LCA1KTtcbiAgICB9O1xuXG5cbiAgICByZXR1cm4gbXk7XG59KENoYXQgfHwge30pKTtcbm1vZHVsZS5leHBvcnRzID0gQ2hhdDsiLCIvKipcbiAqIExpc3Qgd2l0aCBzdXBwb3J0ZWQgY29tbWFuZHMuIFRoZSBrZXlzIGFyZSB0aGUgbmFtZXMgb2YgdGhlIGNvbW1hbmRzIGFuZFxuICogdGhlIHZhbHVlIGlzIHRoZSBmdW5jdGlvbiB0aGF0IHByb2Nlc3NlcyB0aGUgbWVzc2FnZS5cbiAqIEB0eXBlIHt7U3RyaW5nOiBmdW5jdGlvbn19XG4gKi9cbnZhciBjb21tYW5kcyA9IHtcbiAgICBcInRvcGljXCIgOiBwcm9jZXNzVG9waWNcbn07XG5cbi8qKlxuICogRXh0cmFjdHMgdGhlIGNvbW1hbmQgZnJvbSB0aGUgbWVzc2FnZS5cbiAqIEBwYXJhbSBtZXNzYWdlIHRoZSByZWNlaXZlZCBtZXNzYWdlXG4gKiBAcmV0dXJucyB7c3RyaW5nfSB0aGUgY29tbWFuZFxuICovXG5mdW5jdGlvbiBnZXRDb21tYW5kKG1lc3NhZ2UpXG57XG4gICAgaWYobWVzc2FnZSlcbiAgICB7XG4gICAgICAgIGZvcih2YXIgY29tbWFuZCBpbiBjb21tYW5kcylcbiAgICAgICAge1xuICAgICAgICAgICAgaWYobWVzc2FnZS5pbmRleE9mKFwiL1wiICsgY29tbWFuZCkgPT0gMClcbiAgICAgICAgICAgICAgICByZXR1cm4gY29tbWFuZDtcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gXCJcIjtcbn07XG5cbi8qKlxuICogUHJvY2Vzc2VzIHRoZSBkYXRhIGZvciB0b3BpYyBjb21tYW5kLlxuICogQHBhcmFtIGNvbW1hbmRBcmd1bWVudHMgdGhlIGFyZ3VtZW50cyBvZiB0aGUgdG9waWMgY29tbWFuZC5cbiAqL1xuZnVuY3Rpb24gcHJvY2Vzc1RvcGljKGNvbW1hbmRBcmd1bWVudHMpXG57XG4gICAgdmFyIHRvcGljID0gVXRpbC5lc2NhcGVIdG1sKGNvbW1hbmRBcmd1bWVudHMpO1xuICAgIHhtcHAuc2V0U3ViamVjdCh0b3BpYyk7XG59XG5cbi8qKlxuICogQ29uc3RydWN0cyBuZXcgQ29tbWFuZFByb2NjZXNzb3IgaW5zdGFuY2UgZnJvbSBhIG1lc3NhZ2UgdGhhdFxuICogaGFuZGxlcyBjb21tYW5kcyByZWNlaXZlZCB2aWEgY2hhdCBtZXNzYWdlcy5cbiAqIEBwYXJhbSBtZXNzYWdlIHRoZSBtZXNzYWdlXG4gKiBAY29uc3RydWN0b3JcbiAqL1xuZnVuY3Rpb24gQ29tbWFuZHNQcm9jZXNzb3IobWVzc2FnZSlcbntcblxuXG4gICAgdmFyIGNvbW1hbmQgPSBnZXRDb21tYW5kKG1lc3NhZ2UpO1xuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgbmFtZSBvZiB0aGUgY29tbWFuZC5cbiAgICAgKiBAcmV0dXJucyB7U3RyaW5nfSB0aGUgY29tbWFuZFxuICAgICAqL1xuICAgIHRoaXMuZ2V0Q29tbWFuZCA9IGZ1bmN0aW9uKClcbiAgICB7XG4gICAgICAgIHJldHVybiBjb21tYW5kO1xuICAgIH07XG5cblxuICAgIHZhciBtZXNzYWdlQXJndW1lbnQgPSBtZXNzYWdlLnN1YnN0cihjb21tYW5kLmxlbmd0aCArIDIpO1xuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgYXJndW1lbnRzIG9mIHRoZSBjb21tYW5kLlxuICAgICAqIEByZXR1cm5zIHtzdHJpbmd9XG4gICAgICovXG4gICAgdGhpcy5nZXRBcmd1bWVudCA9IGZ1bmN0aW9uKClcbiAgICB7XG4gICAgICAgIHJldHVybiBtZXNzYWdlQXJndW1lbnQ7XG4gICAgfTtcbn1cblxuLyoqXG4gKiBDaGVja3Mgd2hldGhlciB0aGlzIGluc3RhbmNlIGlzIHZhbGlkIGNvbW1hbmQgb3Igbm90LlxuICogQHJldHVybnMge2Jvb2xlYW59XG4gKi9cbkNvbW1hbmRzUHJvY2Vzc29yLnByb3RvdHlwZS5pc0NvbW1hbmQgPSBmdW5jdGlvbigpXG57XG4gICAgaWYodGhpcy5nZXRDb21tYW5kKCkpXG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIHJldHVybiBmYWxzZTtcbn07XG5cbi8qKlxuICogUHJvY2Vzc2VzIHRoZSBjb21tYW5kLlxuICovXG5Db21tYW5kc1Byb2Nlc3Nvci5wcm90b3R5cGUucHJvY2Vzc0NvbW1hbmQgPSBmdW5jdGlvbigpXG57XG4gICAgaWYoIXRoaXMuaXNDb21tYW5kKCkpXG4gICAgICAgIHJldHVybjtcblxuICAgIGNvbW1hbmRzW3RoaXMuZ2V0Q29tbWFuZCgpXSh0aGlzLmdldEFyZ3VtZW50KCkpO1xuXG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IENvbW1hbmRzUHJvY2Vzc29yOyIsInZhciBTbWlsZXlzID0gcmVxdWlyZShcIi4vc21pbGV5cy5qc29uXCIpO1xuLyoqXG4gKiBQcm9jZXNzZXMgbGlua3MgYW5kIHNtaWxleXMgaW4gXCJib2R5XCJcbiAqL1xuZnVuY3Rpb24gcHJvY2Vzc1JlcGxhY2VtZW50cyhib2R5KVxue1xuICAgIC8vbWFrZSBsaW5rcyBjbGlja2FibGVcbiAgICBib2R5ID0gbGlua2lmeShib2R5KTtcblxuICAgIC8vYWRkIHNtaWxleXNcbiAgICBib2R5ID0gc21pbGlmeShib2R5KTtcblxuICAgIHJldHVybiBib2R5O1xufVxuXG4vKipcbiAqIEZpbmRzIGFuZCByZXBsYWNlcyBhbGwgbGlua3MgaW4gdGhlIGxpbmtzIGluIFwiYm9keVwiXG4gKiB3aXRoIHRoZWlyIDxhIGhyZWY9XCJcIj48L2E+XG4gKi9cbmZ1bmN0aW9uIGxpbmtpZnkoaW5wdXRUZXh0KVxue1xuICAgIHZhciByZXBsYWNlZFRleHQsIHJlcGxhY2VQYXR0ZXJuMSwgcmVwbGFjZVBhdHRlcm4yLCByZXBsYWNlUGF0dGVybjM7XG5cbiAgICAvL1VSTHMgc3RhcnRpbmcgd2l0aCBodHRwOi8vLCBodHRwczovLywgb3IgZnRwOi8vXG4gICAgcmVwbGFjZVBhdHRlcm4xID0gLyhcXGIoaHR0cHM/fGZ0cCk6XFwvXFwvWy1BLVowLTkrJkAjXFwvJT89fl98ITosLjtdKlstQS1aMC05KyZAI1xcLyU9fl98XSkvZ2ltO1xuICAgIHJlcGxhY2VkVGV4dCA9IGlucHV0VGV4dC5yZXBsYWNlKHJlcGxhY2VQYXR0ZXJuMSwgJzxhIGhyZWY9XCIkMVwiIHRhcmdldD1cIl9ibGFua1wiPiQxPC9hPicpO1xuXG4gICAgLy9VUkxzIHN0YXJ0aW5nIHdpdGggXCJ3d3cuXCIgKHdpdGhvdXQgLy8gYmVmb3JlIGl0LCBvciBpdCdkIHJlLWxpbmsgdGhlIG9uZXMgZG9uZSBhYm92ZSkuXG4gICAgcmVwbGFjZVBhdHRlcm4yID0gLyhefFteXFwvXSkod3d3XFwuW1xcU10rKFxcYnwkKSkvZ2ltO1xuICAgIHJlcGxhY2VkVGV4dCA9IHJlcGxhY2VkVGV4dC5yZXBsYWNlKHJlcGxhY2VQYXR0ZXJuMiwgJyQxPGEgaHJlZj1cImh0dHA6Ly8kMlwiIHRhcmdldD1cIl9ibGFua1wiPiQyPC9hPicpO1xuXG4gICAgLy9DaGFuZ2UgZW1haWwgYWRkcmVzc2VzIHRvIG1haWx0bzo6IGxpbmtzLlxuICAgIHJlcGxhY2VQYXR0ZXJuMyA9IC8oKFthLXpBLVowLTlcXC1cXF9cXC5dKStAW2EtekEtWlxcX10rPyhcXC5bYS16QS1aXXsyLDZ9KSspL2dpbTtcbiAgICByZXBsYWNlZFRleHQgPSByZXBsYWNlZFRleHQucmVwbGFjZShyZXBsYWNlUGF0dGVybjMsICc8YSBocmVmPVwibWFpbHRvOiQxXCI+JDE8L2E+Jyk7XG5cbiAgICByZXR1cm4gcmVwbGFjZWRUZXh0O1xufVxuXG4vKipcbiAqIFJlcGxhY2VzIGNvbW1vbiBzbWlsZXkgc3RyaW5ncyB3aXRoIGltYWdlc1xuICovXG5mdW5jdGlvbiBzbWlsaWZ5KGJvZHkpXG57XG4gICAgaWYoIWJvZHkpIHtcbiAgICAgICAgcmV0dXJuIGJvZHk7XG4gICAgfVxuXG4gICAgdmFyIHJlZ2V4cyA9IFNtaWxleXNbXCJyZWdleHNcIl07XG4gICAgZm9yKHZhciBzbWlsZXkgaW4gcmVnZXhzKSB7XG4gICAgICAgIGlmKHJlZ2V4cy5oYXNPd25Qcm9wZXJ0eShzbWlsZXkpKSB7XG4gICAgICAgICAgICBib2R5ID0gYm9keS5yZXBsYWNlKHJlZ2V4c1tzbWlsZXldLFxuICAgICAgICAgICAgICAgICAgICAnPGltZyBjbGFzcz1cInNtaWxleVwiIHNyYz1cImltYWdlcy9zbWlsZXlzLycgKyBzbWlsZXkgKyAnLnN2Z1wiPicpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIGJvZHk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0ge1xuICAgIHByb2Nlc3NSZXBsYWNlbWVudHM6IHByb2Nlc3NSZXBsYWNlbWVudHMsXG4gICAgbGlua2lmeTogbGlua2lmeVxufTtcbiIsIm1vZHVsZS5leHBvcnRzPXtcbiAgICBcInNtaWxleXNcIjoge1xuICAgICAgICBcInNtaWxleTFcIjogXCI6KVwiLFxuICAgICAgICBcInNtaWxleTJcIjogXCI6KFwiLFxuICAgICAgICBcInNtaWxleTNcIjogXCI6RFwiLFxuICAgICAgICBcInNtaWxleTRcIjogXCIoeSlcIixcbiAgICAgICAgXCJzbWlsZXk1XCI6IFwiIDpQXCIsXG4gICAgICAgIFwic21pbGV5NlwiOiBcIih3YXZlKVwiLFxuICAgICAgICBcInNtaWxleTdcIjogXCIoYmx1c2gpXCIsXG4gICAgICAgIFwic21pbGV5OFwiOiBcIihjaHVja2xlKVwiLFxuICAgICAgICBcInNtaWxleTlcIjogXCIoc2hvY2tlZClcIixcbiAgICAgICAgXCJzbWlsZXkxMFwiOiBcIjoqXCIsXG4gICAgICAgIFwic21pbGV5MTFcIjogXCIobilcIixcbiAgICAgICAgXCJzbWlsZXkxMlwiOiBcIihzZWFyY2gpXCIsXG4gICAgICAgIFwic21pbGV5MTNcIjogXCIgPDNcIixcbiAgICAgICAgXCJzbWlsZXkxNFwiOiBcIihvb3BzKVwiLFxuICAgICAgICBcInNtaWxleTE1XCI6IFwiKGFuZ3J5KVwiLFxuICAgICAgICBcInNtaWxleTE2XCI6IFwiKGFuZ2VsKVwiLFxuICAgICAgICBcInNtaWxleTE3XCI6IFwiKHNpY2spXCIsXG4gICAgICAgIFwic21pbGV5MThcIjogXCI7KFwiLFxuICAgICAgICBcInNtaWxleTE5XCI6IFwiKGJvbWIpXCIsXG4gICAgICAgIFwic21pbGV5MjBcIjogXCIoY2xhcClcIixcbiAgICAgICAgXCJzbWlsZXkyMVwiOiBcIiA7KVwiXG4gICAgfSxcbiAgICBcInJlZ2V4c1wiOiB7XG4gICAgICAgIFwic21pbGV5MlwiOiAvKDotXFwoXFwofDotXFwofDpcXChcXCh8OlxcKHxcXChzYWRcXCkpL2dpLFxuICAgICAgICBcInNtaWxleTNcIjogLyg6LVxcKVxcKXw6XFwpXFwpfFxcKGxvbFxcKXw6LUR8OkQpL2dpLFxuICAgICAgICBcInNtaWxleTFcIjogLyg6LVxcKXw6XFwpKS9naSxcbiAgICAgICAgXCJzbWlsZXk0XCI6IC8oXFwoeVxcKXxcXChZXFwpfFxcKG9rXFwpKS9naSxcbiAgICAgICAgXCJzbWlsZXk1XCI6IC8oOi1QfDpQfDotcHw6cCkvZ2ksXG4gICAgICAgIFwic21pbGV5NlwiOiAvKFxcKHdhdmVcXCkpL2dpLFxuICAgICAgICBcInNtaWxleTdcIjogLyhcXChibHVzaFxcKSkvZ2ksXG4gICAgICAgIFwic21pbGV5OFwiOiAvKFxcKGNodWNrbGVcXCkpL2dpLFxuICAgICAgICBcInNtaWxleTlcIjogLyg6LTB8XFwoc2hvY2tlZFxcKSkvZ2ksXG4gICAgICAgIFwic21pbGV5MTBcIjogLyg6LVxcKnw6XFwqfFxcKGtpc3NcXCkpL2dpLFxuICAgICAgICBcInNtaWxleTExXCI6IC8oXFwoblxcKSkvZ2ksXG4gICAgICAgIFwic21pbGV5MTJcIjogLyhcXChzZWFyY2hcXCkpL2csXG4gICAgICAgIFwic21pbGV5MTNcIjogLyg8M3wmbHQ7M3wmYW1wO2x0OzN8XFwoTFxcKXxcXChsXFwpfFxcKEhcXCl8XFwoaFxcKSkvZ2ksXG4gICAgICAgIFwic21pbGV5MTRcIjogLyhcXChvb3BzXFwpKS9naSxcbiAgICAgICAgXCJzbWlsZXkxNVwiOiAvKFxcKGFuZ3J5XFwpKS9naSxcbiAgICAgICAgXCJzbWlsZXkxNlwiOiAvKFxcKGFuZ2VsXFwpKS9naSxcbiAgICAgICAgXCJzbWlsZXkxN1wiOiAvKFxcKHNpY2tcXCkpL2dpLFxuICAgICAgICBcInNtaWxleTE4XCI6IC8oOy1cXChcXCh8O1xcKFxcKHw7LVxcKHw7XFwofDpcIlxcKHw6XCItXFwofDp+LVxcKHw6flxcKHxcXCh1cHNldFxcKSkvZ2ksXG4gICAgICAgIFwic21pbGV5MTlcIjogLyhcXChib21iXFwpKS9naSxcbiAgICAgICAgXCJzbWlsZXkyMFwiOiAvKFxcKGNsYXBcXCkpL2dpLFxuICAgICAgICBcInNtaWxleTIxXCI6IC8oOy1cXCl8O1xcKXw7LVxcKVxcKXw7XFwpXFwpfDstRHw7RHxcXCh3aW5rXFwpKS9naVxuICAgIH1cbn1cbiIsIlxudmFyIG51bWJlck9mQ29udGFjdHMgPSAwO1xudmFyIG5vdGlmaWNhdGlvbkludGVydmFsO1xuXG4vKipcbiAqIFVwZGF0ZXMgdGhlIG51bWJlciBvZiBwYXJ0aWNpcGFudHMgaW4gdGhlIGNvbnRhY3QgbGlzdCBidXR0b24gYW5kIHNldHNcbiAqIHRoZSBnbG93XG4gKiBAcGFyYW0gZGVsdGEgaW5kaWNhdGVzIHdoZXRoZXIgYSBuZXcgdXNlciBoYXMgam9pbmVkICgxKSBvciBzb21lb25lIGhhc1xuICogbGVmdCgtMSlcbiAqL1xuZnVuY3Rpb24gdXBkYXRlTnVtYmVyT2ZQYXJ0aWNpcGFudHMoZGVsdGEpIHtcbiAgICAvL3doZW4gdGhlIHVzZXIgaXMgYWxvbmUgd2UgZG9uJ3Qgc2hvdyB0aGUgbnVtYmVyIG9mIHBhcnRpY2lwYW50c1xuICAgIGlmKG51bWJlck9mQ29udGFjdHMgPT09IDApIHtcbiAgICAgICAgJChcIiNudW1iZXJPZlBhcnRpY2lwYW50c1wiKS50ZXh0KCcnKTtcbiAgICAgICAgbnVtYmVyT2ZDb250YWN0cyArPSBkZWx0YTtcbiAgICB9IGVsc2UgaWYobnVtYmVyT2ZDb250YWN0cyAhPT0gMCAmJiAhQ29udGFjdExpc3QuaXNWaXNpYmxlKCkpIHtcbiAgICAgICAgQ29udGFjdExpc3Quc2V0VmlzdWFsTm90aWZpY2F0aW9uKHRydWUpO1xuICAgICAgICBudW1iZXJPZkNvbnRhY3RzICs9IGRlbHRhO1xuICAgICAgICAkKFwiI251bWJlck9mUGFydGljaXBhbnRzXCIpLnRleHQobnVtYmVyT2ZDb250YWN0cyk7XG4gICAgfVxufVxuXG4vKipcbiAqIENyZWF0ZXMgdGhlIGF2YXRhciBlbGVtZW50LlxuICpcbiAqIEByZXR1cm4gdGhlIG5ld2x5IGNyZWF0ZWQgYXZhdGFyIGVsZW1lbnRcbiAqL1xuZnVuY3Rpb24gY3JlYXRlQXZhdGFyKGlkKSB7XG4gICAgdmFyIGF2YXRhciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2ltZycpO1xuICAgIGF2YXRhci5jbGFzc05hbWUgPSBcImljb24tYXZhdGFyIGF2YXRhclwiO1xuICAgIGF2YXRhci5zcmMgPSBcImh0dHBzOi8vd3d3LmdyYXZhdGFyLmNvbS9hdmF0YXIvXCIgKyBpZCArIFwiP2Q9d2F2YXRhciZzaXplPTMwXCI7XG5cbiAgICByZXR1cm4gYXZhdGFyO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgdGhlIGRpc3BsYXkgbmFtZSBwYXJhZ3JhcGguXG4gKlxuICogQHBhcmFtIGRpc3BsYXlOYW1lIHRoZSBkaXNwbGF5IG5hbWUgdG8gc2V0XG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZURpc3BsYXlOYW1lUGFyYWdyYXBoKGRpc3BsYXlOYW1lKSB7XG4gICAgdmFyIHAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdwJyk7XG4gICAgcC5pbm5lclRleHQgPSBkaXNwbGF5TmFtZTtcblxuICAgIHJldHVybiBwO1xufVxuXG5cbmZ1bmN0aW9uIHN0b3BHbG93aW5nKGdsb3dlcikge1xuICAgIHdpbmRvdy5jbGVhckludGVydmFsKG5vdGlmaWNhdGlvbkludGVydmFsKTtcbiAgICBub3RpZmljYXRpb25JbnRlcnZhbCA9IGZhbHNlO1xuICAgIGdsb3dlci5yZW1vdmVDbGFzcygnZ2xvd2luZycpO1xuICAgIGlmICghQ29udGFjdExpc3QuaXNWaXNpYmxlKCkpIHtcbiAgICAgICAgZ2xvd2VyLnJlbW92ZUNsYXNzKCdhY3RpdmUnKTtcbiAgICB9XG59XG5cblxuLyoqXG4gKiBDb250YWN0IGxpc3QuXG4gKi9cbnZhciBDb250YWN0TGlzdCA9IHtcbiAgICAvKipcbiAgICAgKiBJbmRpY2F0ZXMgaWYgdGhlIGNoYXQgaXMgY3VycmVudGx5IHZpc2libGUuXG4gICAgICpcbiAgICAgKiBAcmV0dXJuIDx0dD50cnVlPC90dD4gaWYgdGhlIGNoYXQgaXMgY3VycmVudGx5IHZpc2libGUsIDx0dD5mYWxzZTwvdHQ+IC1cbiAgICAgKiBvdGhlcndpc2VcbiAgICAgKi9cbiAgICBpc1Zpc2libGU6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuICQoJyNjb250YWN0bGlzdCcpLmlzKFwiOnZpc2libGVcIik7XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIEFkZHMgYSBjb250YWN0IGZvciB0aGUgZ2l2ZW4gcGVlckppZCBpZiBzdWNoIGRvZXNuJ3QgeWV0IGV4aXN0LlxuICAgICAqXG4gICAgICogQHBhcmFtIHBlZXJKaWQgdGhlIHBlZXJKaWQgY29ycmVzcG9uZGluZyB0byB0aGUgY29udGFjdFxuICAgICAqIEBwYXJhbSBpZCB0aGUgdXNlcidzIGVtYWlsIG9yIHVzZXJJZCB1c2VkIHRvIGdldCB0aGUgdXNlcidzIGF2YXRhclxuICAgICAqL1xuICAgIGVuc3VyZUFkZENvbnRhY3Q6IGZ1bmN0aW9uIChwZWVySmlkLCBpZCkge1xuICAgICAgICB2YXIgcmVzb3VyY2VKaWQgPSBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChwZWVySmlkKTtcblxuICAgICAgICB2YXIgY29udGFjdCA9ICQoJyNjb250YWN0bGlzdD51bD5saVtpZD1cIicgKyByZXNvdXJjZUppZCArICdcIl0nKTtcblxuICAgICAgICBpZiAoIWNvbnRhY3QgfHwgY29udGFjdC5sZW5ndGggPD0gMClcbiAgICAgICAgICAgIENvbnRhY3RMaXN0LmFkZENvbnRhY3QocGVlckppZCwgaWQpO1xuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBBZGRzIGEgY29udGFjdCBmb3IgdGhlIGdpdmVuIHBlZXIgamlkLlxuICAgICAqXG4gICAgICogQHBhcmFtIHBlZXJKaWQgdGhlIGppZCBvZiB0aGUgY29udGFjdCB0byBhZGRcbiAgICAgKiBAcGFyYW0gaWQgdGhlIGVtYWlsIG9yIHVzZXJJZCBvZiB0aGUgdXNlclxuICAgICAqL1xuICAgIGFkZENvbnRhY3Q6IGZ1bmN0aW9uIChwZWVySmlkLCBpZCkge1xuICAgICAgICB2YXIgcmVzb3VyY2VKaWQgPSBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChwZWVySmlkKTtcblxuICAgICAgICB2YXIgY29udGFjdGxpc3QgPSAkKCcjY29udGFjdGxpc3Q+dWwnKTtcblxuICAgICAgICB2YXIgbmV3Q29udGFjdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2xpJyk7XG4gICAgICAgIG5ld0NvbnRhY3QuaWQgPSByZXNvdXJjZUppZDtcbiAgICAgICAgbmV3Q29udGFjdC5jbGFzc05hbWUgPSBcImNsaWNrYWJsZVwiO1xuICAgICAgICBuZXdDb250YWN0Lm9uY2xpY2sgPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgICAgIGlmIChldmVudC5jdXJyZW50VGFyZ2V0LmNsYXNzTmFtZSA9PT0gXCJjbGlja2FibGVcIikge1xuICAgICAgICAgICAgICAgICQoQ29udGFjdExpc3QpLnRyaWdnZXIoJ2NvbnRhY3RjbGlja2VkJywgW3BlZXJKaWRdKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfTtcblxuICAgICAgICBuZXdDb250YWN0LmFwcGVuZENoaWxkKGNyZWF0ZUF2YXRhcihpZCkpO1xuICAgICAgICBuZXdDb250YWN0LmFwcGVuZENoaWxkKGNyZWF0ZURpc3BsYXlOYW1lUGFyYWdyYXBoKFwiUGFydGljaXBhbnRcIikpO1xuXG4gICAgICAgIHZhciBjbEVsZW1lbnQgPSBjb250YWN0bGlzdC5nZXQoMCk7XG5cbiAgICAgICAgaWYgKHJlc291cmNlSmlkID09PSB4bXBwLm15UmVzb3VyY2UoKVxuICAgICAgICAgICAgJiYgJCgnI2NvbnRhY3RsaXN0PnVsIC50aXRsZScpWzBdLm5leHRTaWJsaW5nLm5leHRTaWJsaW5nKSB7XG4gICAgICAgICAgICBjbEVsZW1lbnQuaW5zZXJ0QmVmb3JlKG5ld0NvbnRhY3QsXG4gICAgICAgICAgICAgICAgJCgnI2NvbnRhY3RsaXN0PnVsIC50aXRsZScpWzBdLm5leHRTaWJsaW5nLm5leHRTaWJsaW5nKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGNsRWxlbWVudC5hcHBlbmRDaGlsZChuZXdDb250YWN0KTtcbiAgICAgICAgfVxuICAgICAgICB1cGRhdGVOdW1iZXJPZlBhcnRpY2lwYW50cygxKTtcbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlcyBhIGNvbnRhY3QgZm9yIHRoZSBnaXZlbiBwZWVyIGppZC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBwZWVySmlkIHRoZSBwZWVySmlkIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGNvbnRhY3QgdG8gcmVtb3ZlXG4gICAgICovXG4gICAgcmVtb3ZlQ29udGFjdDogZnVuY3Rpb24gKHBlZXJKaWQpIHtcbiAgICAgICAgdmFyIHJlc291cmNlSmlkID0gU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQocGVlckppZCk7XG5cbiAgICAgICAgdmFyIGNvbnRhY3QgPSAkKCcjY29udGFjdGxpc3Q+dWw+bGlbaWQ9XCInICsgcmVzb3VyY2VKaWQgKyAnXCJdJyk7XG5cbiAgICAgICAgaWYgKGNvbnRhY3QgJiYgY29udGFjdC5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICB2YXIgY29udGFjdGxpc3QgPSAkKCcjY29udGFjdGxpc3Q+dWwnKTtcblxuICAgICAgICAgICAgY29udGFjdGxpc3QuZ2V0KDApLnJlbW92ZUNoaWxkKGNvbnRhY3QuZ2V0KDApKTtcblxuICAgICAgICAgICAgdXBkYXRlTnVtYmVyT2ZQYXJ0aWNpcGFudHMoLTEpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIHNldFZpc3VhbE5vdGlmaWNhdGlvbjogZnVuY3Rpb24gKHNob3csIHN0b3BHbG93aW5nSW4pIHtcbiAgICAgICAgdmFyIGdsb3dlciA9ICQoJyNjb250YWN0TGlzdEJ1dHRvbicpO1xuXG4gICAgICAgIGlmIChzaG93ICYmICFub3RpZmljYXRpb25JbnRlcnZhbCkge1xuICAgICAgICAgICAgbm90aWZpY2F0aW9uSW50ZXJ2YWwgPSB3aW5kb3cuc2V0SW50ZXJ2YWwoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIGdsb3dlci50b2dnbGVDbGFzcygnYWN0aXZlIGdsb3dpbmcnKTtcbiAgICAgICAgICAgIH0sIDgwMCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoIXNob3cgJiYgbm90aWZpY2F0aW9uSW50ZXJ2YWwpIHtcbiAgICAgICAgICAgIHN0b3BHbG93aW5nKGdsb3dlcik7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHN0b3BHbG93aW5nSW4pIHtcbiAgICAgICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIHN0b3BHbG93aW5nKGdsb3dlcik7XG4gICAgICAgICAgICB9LCBzdG9wR2xvd2luZ0luKTtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBzZXRDbGlja2FibGU6IGZ1bmN0aW9uIChyZXNvdXJjZUppZCwgaXNDbGlja2FibGUpIHtcbiAgICAgICAgdmFyIGNvbnRhY3QgPSAkKCcjY29udGFjdGxpc3Q+dWw+bGlbaWQ9XCInICsgcmVzb3VyY2VKaWQgKyAnXCJdJyk7XG4gICAgICAgIGlmIChpc0NsaWNrYWJsZSkge1xuICAgICAgICAgICAgY29udGFjdC5hZGRDbGFzcygnY2xpY2thYmxlJyk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBjb250YWN0LnJlbW92ZUNsYXNzKCdjbGlja2FibGUnKTtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBvbkRpc3BsYXlOYW1lQ2hhbmdlOiBmdW5jdGlvbiAocGVlckppZCwgZGlzcGxheU5hbWUpIHtcbiAgICAgICAgaWYgKHBlZXJKaWQgPT09ICdsb2NhbFZpZGVvQ29udGFpbmVyJylcbiAgICAgICAgICAgIHBlZXJKaWQgPSB4bXBwLm15SmlkKCk7XG5cbiAgICAgICAgdmFyIHJlc291cmNlSmlkID0gU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQocGVlckppZCk7XG5cbiAgICAgICAgdmFyIGNvbnRhY3ROYW1lID0gJCgnI2NvbnRhY3RsaXN0ICMnICsgcmVzb3VyY2VKaWQgKyAnPnAnKTtcblxuICAgICAgICBpZiAoY29udGFjdE5hbWUgJiYgZGlzcGxheU5hbWUgJiYgZGlzcGxheU5hbWUubGVuZ3RoID4gMClcbiAgICAgICAgICAgIGNvbnRhY3ROYW1lLmh0bWwoZGlzcGxheU5hbWUpO1xuICAgIH1cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gQ29udGFjdExpc3Q7IiwidmFyIGVtYWlsID0gJyc7XG52YXIgZGlzcGxheU5hbWUgPSAnJztcbnZhciB1c2VySWQ7XG5cblxuZnVuY3Rpb24gc3VwcG9ydHNMb2NhbFN0b3JhZ2UoKSB7XG4gICAgdHJ5IHtcbiAgICAgICAgcmV0dXJuICdsb2NhbFN0b3JhZ2UnIGluIHdpbmRvdyAmJiB3aW5kb3cubG9jYWxTdG9yYWdlICE9PSBudWxsO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgY29uc29sZS5sb2coXCJsb2NhbHN0b3JhZ2UgaXMgbm90IHN1cHBvcnRlZFwiKTtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbn1cblxuXG5mdW5jdGlvbiBnZW5lcmF0ZVVuaXF1ZUlkKCkge1xuICAgIGZ1bmN0aW9uIF9wOCgpIHtcbiAgICAgICAgcmV0dXJuIChNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDE2KStcIjAwMDAwMDAwMFwiKS5zdWJzdHIoMiw4KTtcbiAgICB9XG4gICAgcmV0dXJuIF9wOCgpICsgX3A4KCkgKyBfcDgoKSArIF9wOCgpO1xufVxuXG5pZihzdXBwb3J0c0xvY2FsU3RvcmFnZSgpKSB7XG4gICAgaWYoIXdpbmRvdy5sb2NhbFN0b3JhZ2Uuaml0c2lNZWV0SWQpIHtcbiAgICAgICAgd2luZG93LmxvY2FsU3RvcmFnZS5qaXRzaU1lZXRJZCA9IGdlbmVyYXRlVW5pcXVlSWQoKTtcbiAgICAgICAgY29uc29sZS5sb2coXCJnZW5lcmF0ZWQgaWRcIiwgd2luZG93LmxvY2FsU3RvcmFnZS5qaXRzaU1lZXRJZCk7XG4gICAgfVxuICAgIHVzZXJJZCA9IHdpbmRvdy5sb2NhbFN0b3JhZ2Uuaml0c2lNZWV0SWQgfHwgJyc7XG4gICAgZW1haWwgPSB3aW5kb3cubG9jYWxTdG9yYWdlLmVtYWlsIHx8ICcnO1xuICAgIGRpc3BsYXlOYW1lID0gd2luZG93LmxvY2FsU3RvcmFnZS5kaXNwbGF5bmFtZSB8fCAnJztcbn0gZWxzZSB7XG4gICAgY29uc29sZS5sb2coXCJsb2NhbCBzdG9yYWdlIGlzIG5vdCBzdXBwb3J0ZWRcIik7XG4gICAgdXNlcklkID0gZ2VuZXJhdGVVbmlxdWVJZCgpO1xufVxuXG52YXIgU2V0dGluZ3MgPVxue1xuICAgIHNldERpc3BsYXlOYW1lOiBmdW5jdGlvbiAobmV3RGlzcGxheU5hbWUpIHtcbiAgICAgICAgZGlzcGxheU5hbWUgPSBuZXdEaXNwbGF5TmFtZTtcbiAgICAgICAgd2luZG93LmxvY2FsU3RvcmFnZS5kaXNwbGF5bmFtZSA9IGRpc3BsYXlOYW1lO1xuICAgICAgICByZXR1cm4gZGlzcGxheU5hbWU7XG4gICAgfSxcbiAgICBzZXRFbWFpbDogZnVuY3Rpb24obmV3RW1haWwpXG4gICAge1xuICAgICAgICBlbWFpbCA9IG5ld0VtYWlsO1xuICAgICAgICB3aW5kb3cubG9jYWxTdG9yYWdlLmVtYWlsID0gbmV3RW1haWw7XG4gICAgICAgIHJldHVybiBlbWFpbDtcbiAgICB9LFxuICAgIGdldFNldHRpbmdzOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBlbWFpbDogZW1haWwsXG4gICAgICAgICAgICBkaXNwbGF5TmFtZTogZGlzcGxheU5hbWUsXG4gICAgICAgICAgICB1aWQ6IHVzZXJJZFxuICAgICAgICB9O1xuICAgIH1cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gU2V0dGluZ3M7XG4iLCJ2YXIgQXZhdGFyID0gcmVxdWlyZShcIi4uLy4uL2F2YXRhci9BdmF0YXJcIik7XG52YXIgU2V0dGluZ3MgPSByZXF1aXJlKFwiLi9TZXR0aW5nc1wiKTtcblxuXG52YXIgU2V0dGluZ3NNZW51ID0ge1xuXG4gICAgdXBkYXRlOiBmdW5jdGlvbigpIHtcbiAgICAgICAgdmFyIG5ld0Rpc3BsYXlOYW1lID0gVXRpbC5lc2NhcGVIdG1sKCQoJyNzZXREaXNwbGF5TmFtZScpLmdldCgwKS52YWx1ZSk7XG4gICAgICAgIHZhciBuZXdFbWFpbCA9IFV0aWwuZXNjYXBlSHRtbCgkKCcjc2V0RW1haWwnKS5nZXQoMCkudmFsdWUpO1xuXG4gICAgICAgIGlmKG5ld0Rpc3BsYXlOYW1lKSB7XG4gICAgICAgICAgICB2YXIgZGlzcGxheU5hbWUgPSBTZXR0aW5ncy5zZXREaXNwbGF5TmFtZShuZXdEaXNwbGF5TmFtZSk7XG4gICAgICAgICAgICB4bXBwLmFkZFRvUHJlc2VuY2UoXCJkaXNwbGF5TmFtZVwiLCBkaXNwbGF5TmFtZSwgdHJ1ZSk7XG4gICAgICAgIH1cblxuXG4gICAgICAgIHhtcHAuYWRkVG9QcmVzZW5jZShcImVtYWlsXCIsIG5ld0VtYWlsKTtcbiAgICAgICAgdmFyIGVtYWlsID0gU2V0dGluZ3Muc2V0RW1haWwobmV3RW1haWwpO1xuXG5cbiAgICAgICAgQXZhdGFyLnNldFVzZXJBdmF0YXIoeG1wcC5teUppZCgpLCBlbWFpbCk7XG4gICAgfSxcblxuICAgIGlzVmlzaWJsZTogZnVuY3Rpb24oKSB7XG4gICAgICAgIHJldHVybiAkKCcjc2V0dGluZ3NtZW51JykuaXMoJzp2aXNpYmxlJyk7XG4gICAgfSxcblxuICAgIHNldERpc3BsYXlOYW1lOiBmdW5jdGlvbihuZXdEaXNwbGF5TmFtZSkge1xuICAgICAgICB2YXIgZGlzcGxheU5hbWUgPSBTZXR0aW5ncy5zZXREaXNwbGF5TmFtZShuZXdEaXNwbGF5TmFtZSk7XG4gICAgICAgICQoJyNzZXREaXNwbGF5TmFtZScpLmdldCgwKS52YWx1ZSA9IGRpc3BsYXlOYW1lO1xuICAgIH0sXG5cbiAgICBvbkRpc3BsYXlOYW1lQ2hhbmdlOiBmdW5jdGlvbihwZWVySmlkLCBuZXdEaXNwbGF5TmFtZSkge1xuICAgICAgICBpZihwZWVySmlkID09PSAnbG9jYWxWaWRlb0NvbnRhaW5lcicgfHxcbiAgICAgICAgICAgIHBlZXJKaWQgPT09IHhtcHAubXlKaWQoKSkge1xuICAgICAgICAgICAgdGhpcy5zZXREaXNwbGF5TmFtZShuZXdEaXNwbGF5TmFtZSk7XG4gICAgICAgIH1cbiAgICB9XG59O1xuXG5cbm1vZHVsZS5leHBvcnRzID0gU2V0dGluZ3NNZW51OyIsInZhciBQYW5lbFRvZ2dsZXIgPSByZXF1aXJlKFwiLi4vc2lkZV9wYW5uZWxzL1NpZGVQYW5lbFRvZ2dsZXJcIik7XG5cbnZhciBidXR0b25IYW5kbGVycyA9IHtcbiAgICBcImJvdHRvbV90b29sYmFyX2NvbnRhY3RfbGlzdFwiOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIEJvdHRvbVRvb2xiYXIudG9nZ2xlQ29udGFjdExpc3QoKTtcbiAgICB9LFxuICAgIFwiYm90dG9tX3Rvb2xiYXJfZmlsbV9zdHJpcFwiOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIEJvdHRvbVRvb2xiYXIudG9nZ2xlRmlsbVN0cmlwKCk7XG4gICAgfSxcbiAgICBcImJvdHRvbV90b29sYmFyX2NoYXRcIjogZnVuY3Rpb24gKCkge1xuICAgICAgICBCb3R0b21Ub29sYmFyLnRvZ2dsZUNoYXQoKTtcbiAgICB9XG59O1xuXG52YXIgQm90dG9tVG9vbGJhciA9IChmdW5jdGlvbiAobXkpIHtcbiAgICBteS5pbml0ID0gZnVuY3Rpb24gKCkge1xuICAgICAgICBmb3IodmFyIGsgaW4gYnV0dG9uSGFuZGxlcnMpXG4gICAgICAgICAgICAkKFwiI1wiICsgaykuY2xpY2soYnV0dG9uSGFuZGxlcnNba10pO1xuICAgIH07XG5cbiAgICBteS50b2dnbGVDaGF0ID0gZnVuY3Rpb24oKSB7XG4gICAgICAgIFBhbmVsVG9nZ2xlci50b2dnbGVDaGF0KCk7XG4gICAgfTtcblxuICAgIG15LnRvZ2dsZUNvbnRhY3RMaXN0ID0gZnVuY3Rpb24oKSB7XG4gICAgICAgIFBhbmVsVG9nZ2xlci50b2dnbGVDb250YWN0TGlzdCgpO1xuICAgIH07XG5cbiAgICBteS50b2dnbGVGaWxtU3RyaXAgPSBmdW5jdGlvbigpIHtcbiAgICAgICAgdmFyIGZpbG1zdHJpcCA9ICQoXCIjcmVtb3RlVmlkZW9zXCIpO1xuICAgICAgICBmaWxtc3RyaXAudG9nZ2xlQ2xhc3MoXCJoaWRkZW5cIik7XG4gICAgfTtcblxuICAgICQoZG9jdW1lbnQpLmJpbmQoXCJyZW1vdGV2aWRlby5yZXNpemVkXCIsIGZ1bmN0aW9uIChldmVudCwgd2lkdGgsIGhlaWdodCkge1xuICAgICAgICB2YXIgYm90dG9tID0gKGhlaWdodCAtICQoJyNib3R0b21Ub29sYmFyJykub3V0ZXJIZWlnaHQoKSkvMiArIDE4O1xuXG4gICAgICAgICQoJyNib3R0b21Ub29sYmFyJykuY3NzKHtib3R0b206IGJvdHRvbSArICdweCd9KTtcbiAgICB9KTtcblxuICAgIHJldHVybiBteTtcbn0oQm90dG9tVG9vbGJhciB8fCB7fSkpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IEJvdHRvbVRvb2xiYXI7XG4iLCIvKiBnbG9iYWwgJCwgaW50ZXJmYWNlQ29uZmlnLCBNb2RlcmF0b3IsIERlc2t0b3BTdHJlYW1pbmcuc2hvd0Rlc2t0b3BTaGFyaW5nQnV0dG9uICovXG5cbnZhciB0b29sYmFyVGltZW91dE9iamVjdCxcbiAgICB0b29sYmFyVGltZW91dCA9IGludGVyZmFjZUNvbmZpZy5JTklUSUFMX1RPT0xCQVJfVElNRU9VVDtcblxuZnVuY3Rpb24gc2hvd0Rlc2t0b3BTaGFyaW5nQnV0dG9uKCkge1xuICAgIGlmIChkZXNrdG9wc2hhcmluZy5pc0Rlc2t0b3BTaGFyaW5nRW5hYmxlZCgpKSB7XG4gICAgICAgICQoJyNkZXNrdG9wc2hhcmluZycpLmNzcyh7ZGlzcGxheTogXCJpbmxpbmVcIn0pO1xuICAgIH0gZWxzZSB7XG4gICAgICAgICQoJyNkZXNrdG9wc2hhcmluZycpLmNzcyh7ZGlzcGxheTogXCJub25lXCJ9KTtcbiAgICB9XG59XG5cbi8qKlxuICogSGlkZXMgdGhlIHRvb2xiYXIuXG4gKi9cbmZ1bmN0aW9uIGhpZGVUb29sYmFyKCkge1xuICAgIHZhciBoZWFkZXIgPSAkKFwiI2hlYWRlclwiKSxcbiAgICAgICAgYm90dG9tVG9vbGJhciA9ICQoXCIjYm90dG9tVG9vbGJhclwiKTtcbiAgICB2YXIgaXNUb29sYmFySG92ZXIgPSBmYWxzZTtcbiAgICBoZWFkZXIuZmluZCgnKicpLmVhY2goZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgaWQgPSAkKHRoaXMpLmF0dHIoJ2lkJyk7XG4gICAgICAgIGlmICgkKFwiI1wiICsgaWQgKyBcIjpob3ZlclwiKS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBpc1Rvb2xiYXJIb3ZlciA9IHRydWU7XG4gICAgICAgIH1cbiAgICB9KTtcbiAgICBpZiAoJChcIiNib3R0b21Ub29sYmFyOmhvdmVyXCIpLmxlbmd0aCA+IDApIHtcbiAgICAgICAgaXNUb29sYmFySG92ZXIgPSB0cnVlO1xuICAgIH1cblxuICAgIGNsZWFyVGltZW91dCh0b29sYmFyVGltZW91dE9iamVjdCk7XG4gICAgdG9vbGJhclRpbWVvdXRPYmplY3QgPSBudWxsO1xuXG4gICAgaWYgKCFpc1Rvb2xiYXJIb3Zlcikge1xuICAgICAgICBoZWFkZXIuaGlkZShcInNsaWRlXCIsIHsgZGlyZWN0aW9uOiBcInVwXCIsIGR1cmF0aW9uOiAzMDB9KTtcbiAgICAgICAgJCgnI3N1YmplY3QnKS5hbmltYXRlKHt0b3A6IFwiLT00MFwifSwgMzAwKTtcbiAgICAgICAgaWYgKCQoXCIjcmVtb3RlVmlkZW9zXCIpLmhhc0NsYXNzKFwiaGlkZGVuXCIpKSB7XG4gICAgICAgICAgICBib3R0b21Ub29sYmFyLmhpZGUoXG4gICAgICAgICAgICAgICAgXCJzbGlkZVwiLCB7ZGlyZWN0aW9uOiBcInJpZ2h0XCIsIGR1cmF0aW9uOiAzMDB9KTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgdG9vbGJhclRpbWVvdXRPYmplY3QgPSBzZXRUaW1lb3V0KGhpZGVUb29sYmFyLCB0b29sYmFyVGltZW91dCk7XG4gICAgfVxufVxuXG52YXIgVG9vbGJhclRvZ2dsZXIgPSB7XG4gICAgLyoqXG4gICAgICogU2hvd3MgdGhlIG1haW4gdG9vbGJhci5cbiAgICAgKi9cbiAgICBzaG93VG9vbGJhcjogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgaGVhZGVyID0gJChcIiNoZWFkZXJcIiksXG4gICAgICAgICAgICBib3R0b21Ub29sYmFyID0gJChcIiNib3R0b21Ub29sYmFyXCIpO1xuICAgICAgICBpZiAoIWhlYWRlci5pcygnOnZpc2libGUnKSB8fCAhYm90dG9tVG9vbGJhci5pcyhcIjp2aXNpYmxlXCIpKSB7XG4gICAgICAgICAgICBoZWFkZXIuc2hvdyhcInNsaWRlXCIsIHsgZGlyZWN0aW9uOiBcInVwXCIsIGR1cmF0aW9uOiAzMDB9KTtcbiAgICAgICAgICAgICQoJyNzdWJqZWN0JykuYW5pbWF0ZSh7dG9wOiBcIis9NDBcIn0sIDMwMCk7XG4gICAgICAgICAgICBpZiAoIWJvdHRvbVRvb2xiYXIuaXMoXCI6dmlzaWJsZVwiKSkge1xuICAgICAgICAgICAgICAgIGJvdHRvbVRvb2xiYXIuc2hvdyhcbiAgICAgICAgICAgICAgICAgICAgXCJzbGlkZVwiLCB7ZGlyZWN0aW9uOiBcInJpZ2h0XCIsIGR1cmF0aW9uOiAzMDB9KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHRvb2xiYXJUaW1lb3V0T2JqZWN0KSB7XG4gICAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRvb2xiYXJUaW1lb3V0T2JqZWN0KTtcbiAgICAgICAgICAgICAgICB0b29sYmFyVGltZW91dE9iamVjdCA9IG51bGw7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0b29sYmFyVGltZW91dE9iamVjdCA9IHNldFRpbWVvdXQoaGlkZVRvb2xiYXIsIHRvb2xiYXJUaW1lb3V0KTtcbiAgICAgICAgICAgIHRvb2xiYXJUaW1lb3V0ID0gaW50ZXJmYWNlQ29uZmlnLlRPT0xCQVJfVElNRU9VVDtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh4bXBwLmlzTW9kZXJhdG9yKCkpXG4gICAgICAgIHtcbi8vICAgICAgICAgICAgVE9ETzogRW5hYmxlIHNldHRpbmdzIGZ1bmN0aW9uYWxpdHkuXG4vLyAgICAgICAgICAgICAgICAgIE5lZWQgdG8gdW5jb21tZW50IHRoZSBzZXR0aW5ncyBidXR0b24gaW4gaW5kZXguaHRtbC5cbi8vICAgICAgICAgICAgJCgnI3NldHRpbmdzQnV0dG9uJykuY3NzKHt2aXNpYmlsaXR5OlwidmlzaWJsZVwifSk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBTaG93L2hpZGUgZGVza3RvcCBzaGFyaW5nIGJ1dHRvblxuICAgICAgICBzaG93RGVza3RvcFNoYXJpbmdCdXR0b24oKTtcbiAgICB9LFxuXG5cbiAgICAvKipcbiAgICAgKiBEb2Nrcy91bmRvY2tzIHRoZSB0b29sYmFyLlxuICAgICAqXG4gICAgICogQHBhcmFtIGlzRG9jayBpbmRpY2F0ZXMgd2hhdCBvcGVyYXRpb24gdG8gcGVyZm9ybVxuICAgICAqL1xuICAgIGRvY2tUb29sYmFyOiBmdW5jdGlvbiAoaXNEb2NrKSB7XG4gICAgICAgIGlmIChpc0RvY2spIHtcbiAgICAgICAgICAgIC8vIEZpcnN0IG1ha2Ugc3VyZSB0aGUgdG9vbGJhciBpcyBzaG93bi5cbiAgICAgICAgICAgIGlmICghJCgnI2hlYWRlcicpLmlzKCc6dmlzaWJsZScpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5zaG93VG9vbGJhcigpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBUaGVuIGNsZWFyIHRoZSB0aW1lIG91dCwgdG8gZG9jayB0aGUgdG9vbGJhci5cbiAgICAgICAgICAgIGlmICh0b29sYmFyVGltZW91dE9iamVjdCkge1xuICAgICAgICAgICAgICAgIGNsZWFyVGltZW91dCh0b29sYmFyVGltZW91dE9iamVjdCk7XG4gICAgICAgICAgICAgICAgdG9vbGJhclRpbWVvdXRPYmplY3QgPSBudWxsO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgaWYgKCEkKCcjaGVhZGVyJykuaXMoJzp2aXNpYmxlJykpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnNob3dUb29sYmFyKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB0b29sYmFyVGltZW91dE9iamVjdCA9IHNldFRpbWVvdXQoaGlkZVRvb2xiYXIsIHRvb2xiYXJUaW1lb3V0KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBzaG93RGVza3RvcFNoYXJpbmdCdXR0b246IHNob3dEZXNrdG9wU2hhcmluZ0J1dHRvblxuXG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IFRvb2xiYXJUb2dnbGVyOyIsIi8qIGdsb2JhbCAkLCBidXR0b25DbGljaywgY29uZmlnLCBsb2NrUm9vbSxcbiAgIHNldFNoYXJlZEtleSwgVXRpbCAqL1xudmFyIG1lc3NhZ2VIYW5kbGVyID0gcmVxdWlyZShcIi4uL3V0aWwvTWVzc2FnZUhhbmRsZXJcIik7XG52YXIgQm90dG9tVG9vbGJhciA9IHJlcXVpcmUoXCIuL0JvdHRvbVRvb2xiYXJcIik7XG52YXIgUHJlemkgPSByZXF1aXJlKFwiLi4vcHJlemkvUHJlemlcIik7XG52YXIgRXRoZXJwYWQgPSByZXF1aXJlKFwiLi4vZXRoZXJwYWQvRXRoZXJwYWRcIik7XG52YXIgUGFuZWxUb2dnbGVyID0gcmVxdWlyZShcIi4uL3NpZGVfcGFubmVscy9TaWRlUGFuZWxUb2dnbGVyXCIpO1xudmFyIEF1dGhlbnRpY2F0aW9uID0gcmVxdWlyZShcIi4uL2F1dGhlbnRpY2F0aW9uL0F1dGhlbnRpY2F0aW9uXCIpO1xudmFyIFVJVXRpbCA9IHJlcXVpcmUoXCIuLi91dGlsL1VJVXRpbFwiKTtcblxudmFyIHJvb21VcmwgPSBudWxsO1xudmFyIHNoYXJlZEtleSA9ICcnO1xudmFyIFVJID0gbnVsbDtcblxudmFyIGJ1dHRvbkhhbmRsZXJzID1cbntcbiAgICBcInRvb2xiYXJfYnV0dG9uX211dGVcIjogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gVUkudG9nZ2xlQXVkaW8oKTtcbiAgICB9LFxuICAgIFwidG9vbGJhcl9idXR0b25fY2FtZXJhXCI6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIFVJLnRvZ2dsZVZpZGVvKCk7XG4gICAgfSxcbiAgICBcInRvb2xiYXJfYnV0dG9uX2F1dGhlbnRpY2F0aW9uXCI6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIFRvb2xiYXIuYXV0aGVudGljYXRlQ2xpY2tlZCgpO1xuICAgIH0sXG4gICAgXCJ0b29sYmFyX2J1dHRvbl9yZWNvcmRcIjogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gdG9nZ2xlUmVjb3JkaW5nKCk7XG4gICAgfSxcbiAgICBcInRvb2xiYXJfYnV0dG9uX3NlY3VyaXR5XCI6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIFRvb2xiYXIub3BlbkxvY2tEaWFsb2coKTtcbiAgICB9LFxuICAgIFwidG9vbGJhcl9idXR0b25fbGlua1wiOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBUb29sYmFyLm9wZW5MaW5rRGlhbG9nKCk7XG4gICAgfSxcbiAgICBcInRvb2xiYXJfYnV0dG9uX2NoYXRcIjogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gQm90dG9tVG9vbGJhci50b2dnbGVDaGF0KCk7XG4gICAgfSxcbiAgICBcInRvb2xiYXJfYnV0dG9uX3ByZXppXCI6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIFByZXppLm9wZW5QcmV6aURpYWxvZygpO1xuICAgIH0sXG4gICAgXCJ0b29sYmFyX2J1dHRvbl9ldGhlcnBhZFwiOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBFdGhlcnBhZC50b2dnbGVFdGhlcnBhZCgwKTtcbiAgICB9LFxuICAgIFwidG9vbGJhcl9idXR0b25fZGVza3RvcHNoYXJpbmdcIjogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gZGVza3RvcHNoYXJpbmcudG9nZ2xlU2NyZWVuU2hhcmluZygpO1xuICAgIH0sXG4gICAgXCJ0b29sYmFyX2J1dHRvbl9mdWxsU2NyZWVuXCI6IGZ1bmN0aW9uKClcbiAgICB7XG4gICAgICAgIFVJVXRpbC5idXR0b25DbGljayhcIiNmdWxsU2NyZWVuXCIsIFwiaWNvbi1mdWxsLXNjcmVlbiBpY29uLWV4aXQtZnVsbC1zY3JlZW5cIik7XG4gICAgICAgIHJldHVybiBUb29sYmFyLnRvZ2dsZUZ1bGxTY3JlZW4oKTtcbiAgICB9LFxuICAgIFwidG9vbGJhcl9idXR0b25fc2lwXCI6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIGNhbGxTaXBCdXR0b25DbGlja2VkKCk7XG4gICAgfSxcbiAgICBcInRvb2xiYXJfYnV0dG9uX3NldHRpbmdzXCI6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgUGFuZWxUb2dnbGVyLnRvZ2dsZVNldHRpbmdzTWVudSgpO1xuICAgIH0sXG4gICAgXCJ0b29sYmFyX2J1dHRvbl9oYW5ndXBcIjogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gaGFuZ3VwKCk7XG4gICAgfVxufTtcblxuZnVuY3Rpb24gaGFuZ3VwKCkge1xuICAgIHhtcHAuZGlzcG9zZUNvbmZlcmVuY2UoKTtcbiAgICBpZihjb25maWcuZW5hYmxlV2VsY29tZVBhZ2UpXG4gICAge1xuICAgICAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uKClcbiAgICAgICAge1xuICAgICAgICAgICAgd2luZG93LmxvY2FsU3RvcmFnZS53ZWxjb21lUGFnZURpc2FibGVkID0gZmFsc2U7XG4gICAgICAgICAgICB3aW5kb3cubG9jYXRpb24ucGF0aG5hbWUgPSBcIi9cIjtcbiAgICAgICAgfSwgMTAwMDApO1xuXG4gICAgfVxuXG4gICAgVUkubWVzc2FnZUhhbmRsZXIub3BlbkRpYWxvZyhcbiAgICAgICAgXCJTZXNzaW9uIFRlcm1pbmF0ZWRcIixcbiAgICAgICAgXCJZb3UgaHVuZyB1cCB0aGUgY2FsbFwiLFxuICAgICAgICB0cnVlLFxuICAgICAgICB7IFwiSm9pbiBhZ2FpblwiOiB0cnVlIH0sXG4gICAgICAgIGZ1bmN0aW9uKGV2ZW50LCB2YWx1ZSwgbWVzc2FnZSwgZm9ybVZhbHMpXG4gICAgICAgIHtcbiAgICAgICAgICAgIHdpbmRvdy5sb2NhdGlvbi5yZWxvYWQoKTtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICk7XG59XG5cbi8qKlxuICogU3RhcnRzIG9yIHN0b3BzIHRoZSByZWNvcmRpbmcgZm9yIHRoZSBjb25mZXJlbmNlLlxuICovXG5cbmZ1bmN0aW9uIHRvZ2dsZVJlY29yZGluZygpIHtcbiAgICB4bXBwLnRvZ2dsZVJlY29yZGluZyhmdW5jdGlvbiAoY2FsbGJhY2spIHtcbiAgICAgICAgVUkubWVzc2FnZUhhbmRsZXIub3BlblR3b0J1dHRvbkRpYWxvZyhudWxsLFxuICAgICAgICAgICAgICAgICc8aDI+RW50ZXIgcmVjb3JkaW5nIHRva2VuPC9oMj4nICtcbiAgICAgICAgICAgICAgICAnPGlucHV0IGlkPVwicmVjb3JkaW5nVG9rZW5cIiB0eXBlPVwidGV4dFwiICcgK1xuICAgICAgICAgICAgICAgICdwbGFjZWhvbGRlcj1cInRva2VuXCIgYXV0b2ZvY3VzPicsXG4gICAgICAgICAgICBmYWxzZSxcbiAgICAgICAgICAgIFwiU2F2ZVwiLFxuICAgICAgICAgICAgZnVuY3Rpb24gKGUsIHYsIG0sIGYpIHtcbiAgICAgICAgICAgICAgICBpZiAodikge1xuICAgICAgICAgICAgICAgICAgICB2YXIgdG9rZW4gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgncmVjb3JkaW5nVG9rZW4nKTtcblxuICAgICAgICAgICAgICAgICAgICBpZiAodG9rZW4udmFsdWUpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrKFV0aWwuZXNjYXBlSHRtbCh0b2tlbi52YWx1ZSkpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICAgICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdyZWNvcmRpbmdUb2tlbicpLmZvY3VzKCk7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgfVxuICAgICAgICApO1xuICAgIH0sIFRvb2xiYXIuc2V0UmVjb3JkaW5nQnV0dG9uU3RhdGUsIFRvb2xiYXIuc2V0UmVjb3JkaW5nQnV0dG9uU3RhdGUpO1xufVxuXG4vKipcbiAqIExvY2tzIC8gdW5sb2NrcyB0aGUgcm9vbS5cbiAqL1xuZnVuY3Rpb24gbG9ja1Jvb20obG9jaykge1xuICAgIHZhciBjdXJyZW50U2hhcmVkS2V5ID0gJyc7XG4gICAgaWYgKGxvY2spXG4gICAgICAgIGN1cnJlbnRTaGFyZWRLZXkgPSBzaGFyZWRLZXk7XG5cbiAgICB4bXBwLmxvY2tSb29tKGN1cnJlbnRTaGFyZWRLZXksIGZ1bmN0aW9uIChyZXMpIHtcbiAgICAgICAgLy8gcGFzc3dvcmQgaXMgcmVxdWlyZWRcbiAgICAgICAgaWYgKHNoYXJlZEtleSlcbiAgICAgICAge1xuICAgICAgICAgICAgY29uc29sZS5sb2coJ3NldCByb29tIHBhc3N3b3JkJyk7XG4gICAgICAgICAgICBUb29sYmFyLmxvY2tMb2NrQnV0dG9uKCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZVxuICAgICAgICB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZygncmVtb3ZlZCByb29tIHBhc3N3b3JkJyk7XG4gICAgICAgICAgICBUb29sYmFyLnVubG9ja0xvY2tCdXR0b24oKTtcbiAgICAgICAgfVxuICAgIH0sIGZ1bmN0aW9uIChlcnIpIHtcbiAgICAgICAgY29uc29sZS53YXJuKCdzZXR0aW5nIHBhc3N3b3JkIGZhaWxlZCcsIGVycik7XG4gICAgICAgIG1lc3NhZ2VIYW5kbGVyLnNob3dFcnJvcignTG9jayBmYWlsZWQnLFxuICAgICAgICAgICAgJ0ZhaWxlZCB0byBsb2NrIGNvbmZlcmVuY2UuJyxcbiAgICAgICAgICAgIGVycik7XG4gICAgICAgIFRvb2xiYXIuc2V0U2hhcmVkS2V5KCcnKTtcbiAgICB9LCBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGNvbnNvbGUud2Fybigncm9vbSBwYXNzd29yZHMgbm90IHN1cHBvcnRlZCcpO1xuICAgICAgICBtZXNzYWdlSGFuZGxlci5zaG93RXJyb3IoJ1dhcm5pbmcnLFxuICAgICAgICAgICAgJ1Jvb20gcGFzc3dvcmRzIGFyZSBjdXJyZW50bHkgbm90IHN1cHBvcnRlZC4nKTtcbiAgICAgICAgVG9vbGJhci5zZXRTaGFyZWRLZXkoJycpO1xuICAgIH0pO1xufTtcblxuLyoqXG4gKiBJbnZpdGUgcGFydGljaXBhbnRzIHRvIGNvbmZlcmVuY2UuXG4gKi9cbmZ1bmN0aW9uIGludml0ZVBhcnRpY2lwYW50cygpIHtcbiAgICBpZiAocm9vbVVybCA9PT0gbnVsbClcbiAgICAgICAgcmV0dXJuO1xuXG4gICAgdmFyIHNoYXJlZEtleVRleHQgPSBcIlwiO1xuICAgIGlmIChzaGFyZWRLZXkgJiYgc2hhcmVkS2V5Lmxlbmd0aCA+IDApIHtcbiAgICAgICAgc2hhcmVkS2V5VGV4dCA9XG4gICAgICAgICAgICBcIlRoaXMgY29uZmVyZW5jZSBpcyBwYXNzd29yZCBwcm90ZWN0ZWQuIFBsZWFzZSB1c2UgdGhlIFwiICtcbiAgICAgICAgICAgIFwiZm9sbG93aW5nIHBpbiB3aGVuIGpvaW5pbmc6JTBEJTBBJTBEJTBBXCIgK1xuICAgICAgICAgICAgc2hhcmVkS2V5ICsgXCIlMEQlMEElMEQlMEFcIjtcbiAgICB9XG5cbiAgICB2YXIgY29uZmVyZW5jZU5hbWUgPSByb29tVXJsLnN1YnN0cmluZyhyb29tVXJsLmxhc3RJbmRleE9mKCcvJykgKyAxKTtcbiAgICB2YXIgc3ViamVjdCA9IFwiSW52aXRhdGlvbiB0byBhIFwiICsgaW50ZXJmYWNlQ29uZmlnLkFQUF9OQU1FICsgXCIgKFwiICsgY29uZmVyZW5jZU5hbWUgKyBcIilcIjtcbiAgICB2YXIgYm9keSA9IFwiSGV5IHRoZXJlLCBJJTI3ZCBsaWtlIHRvIGludml0ZSB5b3UgdG8gYSBcIiArIGludGVyZmFjZUNvbmZpZy5BUFBfTkFNRSArXG4gICAgICAgIFwiIGNvbmZlcmVuY2UgSSUyN3ZlIGp1c3Qgc2V0IHVwLiUwRCUwQSUwRCUwQVwiICtcbiAgICAgICAgXCJQbGVhc2UgY2xpY2sgb24gdGhlIGZvbGxvd2luZyBsaW5rIGluIG9yZGVyXCIgK1xuICAgICAgICBcIiB0byBqb2luIHRoZSBjb25mZXJlbmNlLiUwRCUwQSUwRCUwQVwiICtcbiAgICAgICAgcm9vbVVybCArXG4gICAgICAgIFwiJTBEJTBBJTBEJTBBXCIgK1xuICAgICAgICBzaGFyZWRLZXlUZXh0ICtcbiAgICAgICAgXCJOb3RlIHRoYXQgXCIgKyBpbnRlcmZhY2VDb25maWcuQVBQX05BTUUgKyBcIiBpcyBjdXJyZW50bHlcIiArXG4gICAgICAgIFwiIG9ubHkgc3VwcG9ydGVkIGJ5IENocm9taXVtLFwiICtcbiAgICAgICAgXCIgR29vZ2xlIENocm9tZSBhbmQgT3BlcmEsIHNvIHlvdSBuZWVkXCIgK1xuICAgICAgICBcIiB0byBiZSB1c2luZyBvbmUgb2YgdGhlc2UgYnJvd3NlcnMuJTBEJTBBJTBEJTBBXCIgK1xuICAgICAgICBcIlRhbGsgdG8geW91IGluIGEgc2VjIVwiO1xuXG4gICAgaWYgKHdpbmRvdy5sb2NhbFN0b3JhZ2UuZGlzcGxheW5hbWUpIHtcbiAgICAgICAgYm9keSArPSBcIiUwRCUwQSUwRCUwQVwiICsgd2luZG93LmxvY2FsU3RvcmFnZS5kaXNwbGF5bmFtZTtcbiAgICB9XG5cbiAgICBpZiAoaW50ZXJmYWNlQ29uZmlnLklOVklUQVRJT05fUE9XRVJFRF9CWSkge1xuICAgICAgICBib2R5ICs9IFwiJTBEJTBBJTBEJTBBLS0lMEQlMEFwb3dlcmVkIGJ5IGppdHNpLm9yZ1wiO1xuICAgIH1cblxuICAgIHdpbmRvdy5vcGVuKFwibWFpbHRvOj9zdWJqZWN0PVwiICsgc3ViamVjdCArIFwiJmJvZHk9XCIgKyBib2R5LCAnX2JsYW5rJyk7XG59XG5cbmZ1bmN0aW9uIGNhbGxTaXBCdXR0b25DbGlja2VkKClcbntcbiAgICB2YXIgZGVmYXVsdE51bWJlclxuICAgICAgICA9IGNvbmZpZy5kZWZhdWx0U2lwTnVtYmVyID8gY29uZmlnLmRlZmF1bHRTaXBOdW1iZXIgOiAnJztcblxuICAgIG1lc3NhZ2VIYW5kbGVyLm9wZW5Ud29CdXR0b25EaWFsb2cobnVsbCxcbiAgICAgICAgJzxoMj5FbnRlciBTSVAgbnVtYmVyPC9oMj4nICtcbiAgICAgICAgJzxpbnB1dCBpZD1cInNpcE51bWJlclwiIHR5cGU9XCJ0ZXh0XCInICtcbiAgICAgICAgJyB2YWx1ZT1cIicgKyBkZWZhdWx0TnVtYmVyICsgJ1wiIGF1dG9mb2N1cz4nLFxuICAgICAgICBmYWxzZSxcbiAgICAgICAgXCJEaWFsXCIsXG4gICAgICAgIGZ1bmN0aW9uIChlLCB2LCBtLCBmKSB7XG4gICAgICAgICAgICBpZiAodikge1xuICAgICAgICAgICAgICAgIHZhciBudW1iZXJJbnB1dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzaXBOdW1iZXInKTtcbiAgICAgICAgICAgICAgICBpZiAobnVtYmVySW5wdXQudmFsdWUpIHtcbiAgICAgICAgICAgICAgICAgICAgeG1wcC5kaWFsKG51bWJlcklucHV0LnZhbHVlLCAnZnJvbW51bWJlcicsXG4gICAgICAgICAgICAgICAgICAgICAgICBVSS5nZXRSb29tTmFtZSgpLCBzaGFyZWRLZXkpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc2lwTnVtYmVyJykuZm9jdXMoKTtcbiAgICAgICAgfVxuICAgICk7XG59XG5cbnZhciBUb29sYmFyID0gKGZ1bmN0aW9uIChteSkge1xuXG4gICAgbXkuaW5pdCA9IGZ1bmN0aW9uICh1aSkge1xuICAgICAgICBmb3IodmFyIGsgaW4gYnV0dG9uSGFuZGxlcnMpXG4gICAgICAgICAgICAkKFwiI1wiICsgaykuY2xpY2soYnV0dG9uSGFuZGxlcnNba10pO1xuICAgICAgICBVSSA9IHVpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFNldHMgc2hhcmVkIGtleVxuICAgICAqIEBwYXJhbSBzS2V5IHRoZSBzaGFyZWQga2V5XG4gICAgICovXG4gICAgbXkuc2V0U2hhcmVkS2V5ID0gZnVuY3Rpb24gKHNLZXkpIHtcbiAgICAgICAgc2hhcmVkS2V5ID0gc0tleTtcbiAgICB9O1xuXG4gICAgbXkuYXV0aGVudGljYXRlQ2xpY2tlZCA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgQXV0aGVudGljYXRpb24uZm9jdXNBdXRoZW50aWNhdGlvbldpbmRvdygpO1xuICAgICAgICAvLyBHZXQgYXV0aGVudGljYXRpb24gVVJMXG4gICAgICAgIHhtcHAuZ2V0QXV0aFVybChVSS5nZXRSb29tTmFtZSgpLCBmdW5jdGlvbiAodXJsKSB7XG4gICAgICAgICAgICAvLyBPcGVuIHBvcHVwIHdpdGggYXV0aGVudGljYXRpb24gVVJMXG4gICAgICAgICAgICB2YXIgYXV0aGVudGljYXRpb25XaW5kb3cgPSBBdXRoZW50aWNhdGlvbi5jcmVhdGVBdXRoZW50aWNhdGlvbldpbmRvdyhmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgLy8gT24gcG9wdXAgY2xvc2VkIC0gcmV0cnkgcm9vbSBhbGxvY2F0aW9uXG4gICAgICAgICAgICAgICAgeG1wcC5hbGxvY2F0ZUNvbmZlcmVuY2VGb2N1cyhVSS5nZXRSb29tTmFtZSgpLCBVSS5jaGVja0Zvck5pY2tuYW1lQW5kSm9pbik7XG4gICAgICAgICAgICB9LCB1cmwpO1xuICAgICAgICAgICAgaWYgKCFhdXRoZW50aWNhdGlvbldpbmRvdykge1xuICAgICAgICAgICAgICAgIFRvb2xiYXIuc2hvd0F1dGhlbnRpY2F0ZUJ1dHRvbih0cnVlKTtcbiAgICAgICAgICAgICAgICBtZXNzYWdlSGFuZGxlci5vcGVuTWVzc2FnZURpYWxvZyhcbiAgICAgICAgICAgICAgICAgICAgbnVsbCwgXCJZb3VyIGJyb3dzZXIgaXMgYmxvY2tpbmcgcG9wdXAgd2luZG93cyBmcm9tIHRoaXMgc2l0ZS5cIiArXG4gICAgICAgICAgICAgICAgICAgICAgICBcIiBQbGVhc2UgZW5hYmxlIHBvcHVwcyBpbiB5b3VyIGJyb3dzZXIgc2VjdXJpdHkgc2V0dGluZ3NcIiArXG4gICAgICAgICAgICAgICAgICAgICAgICBcIiBhbmQgdHJ5IGFnYWluLlwiKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIFVwZGF0ZXMgdGhlIHJvb20gaW52aXRlIHVybC5cbiAgICAgKi9cbiAgICBteS51cGRhdGVSb29tVXJsID0gZnVuY3Rpb24gKG5ld1Jvb21VcmwpIHtcbiAgICAgICAgcm9vbVVybCA9IG5ld1Jvb21Vcmw7XG5cbiAgICAgICAgLy8gSWYgdGhlIGludml0ZSBkaWFsb2cgaGFzIGJlZW4gYWxyZWFkeSBvcGVuZWQgd2UgdXBkYXRlIHRoZSBpbmZvcm1hdGlvbi5cbiAgICAgICAgdmFyIGludml0ZUxpbmsgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnaW52aXRlTGlua1JlZicpO1xuICAgICAgICBpZiAoaW52aXRlTGluaykge1xuICAgICAgICAgICAgaW52aXRlTGluay52YWx1ZSA9IHJvb21Vcmw7XG4gICAgICAgICAgICBpbnZpdGVMaW5rLnNlbGVjdCgpO1xuICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2pxaV9zdGF0ZTBfYnV0dG9uSW52aXRlJykuZGlzYWJsZWQgPSBmYWxzZTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBEaXNhYmxlcyBhbmQgZW5hYmxlcyBzb21lIG9mIHRoZSBidXR0b25zLlxuICAgICAqL1xuICAgIG15LnNldHVwQnV0dG9uc0Zyb21Db25maWcgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGlmIChjb25maWcuZGlzYWJsZVByZXppKVxuICAgICAgICB7XG4gICAgICAgICAgICAkKFwiI3ByZXppX2J1dHRvblwiKS5jc3Moe2Rpc3BsYXk6IFwibm9uZVwifSk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogT3BlbnMgdGhlIGxvY2sgcm9vbSBkaWFsb2cuXG4gICAgICovXG4gICAgbXkub3BlbkxvY2tEaWFsb2cgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIC8vIE9ubHkgdGhlIGZvY3VzIGlzIGFibGUgdG8gc2V0IGEgc2hhcmVkIGtleS5cbiAgICAgICAgaWYgKCF4bXBwLmlzTW9kZXJhdG9yKCkpIHtcbiAgICAgICAgICAgIGlmIChzaGFyZWRLZXkpIHtcbiAgICAgICAgICAgICAgICBtZXNzYWdlSGFuZGxlci5vcGVuTWVzc2FnZURpYWxvZyhudWxsLFxuICAgICAgICAgICAgICAgICAgICAgICAgXCJUaGlzIGNvbnZlcnNhdGlvbiBpcyBjdXJyZW50bHkgcHJvdGVjdGVkIGJ5XCIgK1xuICAgICAgICAgICAgICAgICAgICAgICAgXCIgYSBwYXNzd29yZC4gT25seSB0aGUgb3duZXIgb2YgdGhlIGNvbmZlcmVuY2VcIiArXG4gICAgICAgICAgICAgICAgICAgICAgICBcIiBjb3VsZCBzZXQgYSBwYXNzd29yZC5cIixcbiAgICAgICAgICAgICAgICAgICAgZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgIFwiUGFzc3dvcmRcIik7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIG1lc3NhZ2VIYW5kbGVyLm9wZW5NZXNzYWdlRGlhbG9nKG51bGwsXG4gICAgICAgICAgICAgICAgICAgIFwiVGhpcyBjb252ZXJzYXRpb24gaXNuJ3QgY3VycmVudGx5IHByb3RlY3RlZCBieVwiICtcbiAgICAgICAgICAgICAgICAgICAgICAgIFwiIGEgcGFzc3dvcmQuIE9ubHkgdGhlIG93bmVyIG9mIHRoZSBjb25mZXJlbmNlXCIgK1xuICAgICAgICAgICAgICAgICAgICAgICAgXCIgY291bGQgc2V0IGEgcGFzc3dvcmQuXCIsXG4gICAgICAgICAgICAgICAgICAgIGZhbHNlLFxuICAgICAgICAgICAgICAgICAgICBcIlBhc3N3b3JkXCIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgaWYgKHNoYXJlZEtleSkge1xuICAgICAgICAgICAgICAgIG1lc3NhZ2VIYW5kbGVyLm9wZW5Ud29CdXR0b25EaWFsb2cobnVsbCxcbiAgICAgICAgICAgICAgICAgICAgXCJBcmUgeW91IHN1cmUgeW91IHdvdWxkIGxpa2UgdG8gcmVtb3ZlIHlvdXIgcGFzc3dvcmQ/XCIsXG4gICAgICAgICAgICAgICAgICAgIGZhbHNlLFxuICAgICAgICAgICAgICAgICAgICBcIlJlbW92ZVwiLFxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbiAoZSwgdikge1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHYpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBUb29sYmFyLnNldFNoYXJlZEtleSgnJyk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9ja1Jvb20oZmFsc2UpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgbWVzc2FnZUhhbmRsZXIub3BlblR3b0J1dHRvbkRpYWxvZyhudWxsLFxuICAgICAgICAgICAgICAgICAgICAnPGgyPlNldCBhIHBhc3N3b3JkIHRvIGxvY2sgeW91ciByb29tPC9oMj4nICtcbiAgICAgICAgICAgICAgICAgICAgICAgICc8aW5wdXQgaWQ9XCJsb2NrS2V5XCIgdHlwZT1cInRleHRcIicgK1xuICAgICAgICAgICAgICAgICAgICAgICAgJ3BsYWNlaG9sZGVyPVwieW91ciBwYXNzd29yZFwiIGF1dG9mb2N1cz4nLFxuICAgICAgICAgICAgICAgICAgICBmYWxzZSxcbiAgICAgICAgICAgICAgICAgICAgXCJTYXZlXCIsXG4gICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChlLCB2KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAodikge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBsb2NrS2V5ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvY2tLZXknKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChsb2NrS2V5LnZhbHVlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRvb2xiYXIuc2V0U2hhcmVkS2V5KFV0aWwuZXNjYXBlSHRtbChsb2NrS2V5LnZhbHVlKSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvY2tSb29tKHRydWUpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvY2tLZXknKS5mb2N1cygpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBPcGVucyB0aGUgaW52aXRlIGxpbmsgZGlhbG9nLlxuICAgICAqL1xuICAgIG15Lm9wZW5MaW5rRGlhbG9nID0gZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgaW52aXRlTGluaztcbiAgICAgICAgaWYgKHJvb21VcmwgPT09IG51bGwpIHtcbiAgICAgICAgICAgIGludml0ZUxpbmsgPSBcIllvdXIgY29uZmVyZW5jZSBpcyBjdXJyZW50bHkgYmVpbmcgY3JlYXRlZC4uLlwiO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgaW52aXRlTGluayA9IGVuY29kZVVSSShyb29tVXJsKTtcbiAgICAgICAgfVxuICAgICAgICBtZXNzYWdlSGFuZGxlci5vcGVuVHdvQnV0dG9uRGlhbG9nKFxuICAgICAgICAgICAgXCJTaGFyZSB0aGlzIGxpbmsgd2l0aCBldmVyeW9uZSB5b3Ugd2FudCB0byBpbnZpdGVcIixcbiAgICAgICAgICAgICc8aW5wdXQgaWQ9XCJpbnZpdGVMaW5rUmVmXCIgdHlwZT1cInRleHRcIiB2YWx1ZT1cIicgK1xuICAgICAgICAgICAgICAgIGludml0ZUxpbmsgKyAnXCIgb25jbGljaz1cInRoaXMuc2VsZWN0KCk7XCIgcmVhZG9ubHk+JyxcbiAgICAgICAgICAgIGZhbHNlLFxuICAgICAgICAgICAgXCJJbnZpdGVcIixcbiAgICAgICAgICAgIGZ1bmN0aW9uIChlLCB2KSB7XG4gICAgICAgICAgICAgICAgaWYgKHYpIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHJvb21VcmwpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGludml0ZVBhcnRpY2lwYW50cygpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICBpZiAocm9vbVVybCkge1xuICAgICAgICAgICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnaW52aXRlTGlua1JlZicpLnNlbGVjdCgpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdqcWlfc3RhdGUwX2J1dHRvbkludml0ZScpXG4gICAgICAgICAgICAgICAgICAgICAgICAuZGlzYWJsZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogT3BlbnMgdGhlIHNldHRpbmdzIGRpYWxvZy5cbiAgICAgKi9cbiAgICBteS5vcGVuU2V0dGluZ3NEaWFsb2cgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIG1lc3NhZ2VIYW5kbGVyLm9wZW5Ud29CdXR0b25EaWFsb2coXG4gICAgICAgICAgICAnPGgyPkNvbmZpZ3VyZSB5b3VyIGNvbmZlcmVuY2U8L2gyPicgK1xuICAgICAgICAgICAgICAgICc8aW5wdXQgdHlwZT1cImNoZWNrYm94XCIgaWQ9XCJpbml0TXV0ZWRcIj4nICtcbiAgICAgICAgICAgICAgICAnUGFydGljaXBhbnRzIGpvaW4gbXV0ZWQ8YnIvPicgK1xuICAgICAgICAgICAgICAgICc8aW5wdXQgdHlwZT1cImNoZWNrYm94XCIgaWQ9XCJyZXF1aXJlTmlja25hbWVzXCI+JyArXG4gICAgICAgICAgICAgICAgJ1JlcXVpcmUgbmlja25hbWVzPGJyLz48YnIvPicgK1xuICAgICAgICAgICAgICAgICdTZXQgYSBwYXNzd29yZCB0byBsb2NrIHlvdXIgcm9vbTonICtcbiAgICAgICAgICAgICAgICAnPGlucHV0IGlkPVwibG9ja0tleVwiIHR5cGU9XCJ0ZXh0XCIgcGxhY2Vob2xkZXI9XCJ5b3VyIHBhc3N3b3JkXCInICtcbiAgICAgICAgICAgICAgICAnYXV0b2ZvY3VzPicsXG4gICAgICAgICAgICBudWxsLFxuICAgICAgICAgICAgZmFsc2UsXG4gICAgICAgICAgICBcIlNhdmVcIixcbiAgICAgICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9ja0tleScpLmZvY3VzKCk7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgZnVuY3Rpb24gKGUsIHYpIHtcbiAgICAgICAgICAgICAgICBpZiAodikge1xuICAgICAgICAgICAgICAgICAgICBpZiAoJCgnI2luaXRNdXRlZCcpLmlzKFwiOmNoZWNrZWRcIikpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGl0IGlzIGNoZWNrZWRcbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIGlmICgkKCcjcmVxdWlyZU5pY2tuYW1lcycpLmlzKFwiOmNoZWNrZWRcIikpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGl0IGlzIGNoZWNrZWRcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAvKlxuICAgICAgICAgICAgICAgICAgICB2YXIgbG9ja0tleSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsb2NrS2V5Jyk7XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKGxvY2tLZXkudmFsdWUpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHNldFNoYXJlZEtleShsb2NrS2V5LnZhbHVlKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGxvY2tSb29tKHRydWUpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICApO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBUb2dnbGVzIHRoZSBhcHBsaWNhdGlvbiBpbiBhbmQgb3V0IG9mIGZ1bGwgc2NyZWVuIG1vZGVcbiAgICAgKiAoYS5rLmEuIHByZXNlbnRhdGlvbiBtb2RlIGluIENocm9tZSkuXG4gICAgICovXG4gICAgbXkudG9nZ2xlRnVsbFNjcmVlbiA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIGZzRWxlbWVudCA9IGRvY3VtZW50LmRvY3VtZW50RWxlbWVudDtcblxuICAgICAgICBpZiAoIWRvY3VtZW50Lm1vekZ1bGxTY3JlZW4gJiYgIWRvY3VtZW50LndlYmtpdElzRnVsbFNjcmVlbikge1xuICAgICAgICAgICAgLy9FbnRlciBGdWxsIFNjcmVlblxuICAgICAgICAgICAgaWYgKGZzRWxlbWVudC5tb3pSZXF1ZXN0RnVsbFNjcmVlbikge1xuICAgICAgICAgICAgICAgIGZzRWxlbWVudC5tb3pSZXF1ZXN0RnVsbFNjcmVlbigpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgZnNFbGVtZW50LndlYmtpdFJlcXVlc3RGdWxsU2NyZWVuKEVsZW1lbnQuQUxMT1dfS0VZQk9BUkRfSU5QVVQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy9FeGl0IEZ1bGwgU2NyZWVuXG4gICAgICAgICAgICBpZiAoZG9jdW1lbnQubW96Q2FuY2VsRnVsbFNjcmVlbikge1xuICAgICAgICAgICAgICAgIGRvY3VtZW50Lm1vekNhbmNlbEZ1bGxTY3JlZW4oKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgZG9jdW1lbnQud2Via2l0Q2FuY2VsRnVsbFNjcmVlbigpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfTtcbiAgICAvKipcbiAgICAgKiBVbmxvY2tzIHRoZSBsb2NrIGJ1dHRvbiBzdGF0ZS5cbiAgICAgKi9cbiAgICBteS51bmxvY2tMb2NrQnV0dG9uID0gZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAoJChcIiNsb2NrSWNvblwiKS5oYXNDbGFzcyhcImljb24tc2VjdXJpdHktbG9ja2VkXCIpKVxuICAgICAgICAgICAgVUlVdGlsLmJ1dHRvbkNsaWNrKFwiI2xvY2tJY29uXCIsIFwiaWNvbi1zZWN1cml0eSBpY29uLXNlY3VyaXR5LWxvY2tlZFwiKTtcbiAgICB9O1xuICAgIC8qKlxuICAgICAqIFVwZGF0ZXMgdGhlIGxvY2sgYnV0dG9uIHN0YXRlIHRvIGxvY2tlZC5cbiAgICAgKi9cbiAgICBteS5sb2NrTG9ja0J1dHRvbiA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgaWYgKCQoXCIjbG9ja0ljb25cIikuaGFzQ2xhc3MoXCJpY29uLXNlY3VyaXR5XCIpKVxuICAgICAgICAgICAgVUlVdGlsLmJ1dHRvbkNsaWNrKFwiI2xvY2tJY29uXCIsIFwiaWNvbi1zZWN1cml0eSBpY29uLXNlY3VyaXR5LWxvY2tlZFwiKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogU2hvd3Mgb3IgaGlkZXMgYXV0aGVudGljYXRpb24gYnV0dG9uXG4gICAgICogQHBhcmFtIHNob3cgPHR0PnRydWU8L3R0PiB0byBzaG93IG9yIDx0dD5mYWxzZTwvdHQ+IHRvIGhpZGVcbiAgICAgKi9cbiAgICBteS5zaG93QXV0aGVudGljYXRlQnV0dG9uID0gZnVuY3Rpb24gKHNob3cpIHtcbiAgICAgICAgaWYgKHNob3cpIHtcbiAgICAgICAgICAgICQoJyNhdXRoZW50aWNhdGlvbicpLmNzcyh7ZGlzcGxheTogXCJpbmxpbmVcIn0pO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgJCgnI2F1dGhlbnRpY2F0aW9uJykuY3NzKHtkaXNwbGF5OiBcIm5vbmVcIn0pO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIC8vIFNob3dzIG9yIGhpZGVzIHRoZSAncmVjb3JkaW5nJyBidXR0b24uXG4gICAgbXkuc2hvd1JlY29yZGluZ0J1dHRvbiA9IGZ1bmN0aW9uIChzaG93KSB7XG4gICAgICAgIGlmICghY29uZmlnLmVuYWJsZVJlY29yZGluZykge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHNob3cpIHtcbiAgICAgICAgICAgICQoJyNyZWNvcmRpbmcnKS5jc3Moe2Rpc3BsYXk6IFwiaW5saW5lXCJ9KTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICQoJyNyZWNvcmRpbmcnKS5jc3Moe2Rpc3BsYXk6IFwibm9uZVwifSk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgLy8gU2V0cyB0aGUgc3RhdGUgb2YgdGhlIHJlY29yZGluZyBidXR0b25cbiAgICBteS5zZXRSZWNvcmRpbmdCdXR0b25TdGF0ZSA9IGZ1bmN0aW9uIChpc1JlY29yZGluZykge1xuICAgICAgICBpZiAoaXNSZWNvcmRpbmcpIHtcbiAgICAgICAgICAgICQoJyNyZWNvcmRCdXR0b24nKS5yZW1vdmVDbGFzcyhcImljb24tcmVjRW5hYmxlXCIpO1xuICAgICAgICAgICAgJCgnI3JlY29yZEJ1dHRvbicpLmFkZENsYXNzKFwiaWNvbi1yZWNFbmFibGUgYWN0aXZlXCIpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgJCgnI3JlY29yZEJ1dHRvbicpLnJlbW92ZUNsYXNzKFwiaWNvbi1yZWNFbmFibGUgYWN0aXZlXCIpO1xuICAgICAgICAgICAgJCgnI3JlY29yZEJ1dHRvbicpLmFkZENsYXNzKFwiaWNvbi1yZWNFbmFibGVcIik7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgLy8gU2hvd3Mgb3IgaGlkZXMgU0lQIGNhbGxzIGJ1dHRvblxuICAgIG15LnNob3dTaXBDYWxsQnV0dG9uID0gZnVuY3Rpb24gKHNob3cpIHtcbiAgICAgICAgaWYgKHhtcHAuaXNTaXBHYXRld2F5RW5hYmxlZCgpICYmIHNob3cpIHtcbiAgICAgICAgICAgICQoJyNzaXBDYWxsQnV0dG9uJykuY3NzKHtkaXNwbGF5OiBcImlubGluZVwifSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAkKCcjc2lwQ2FsbEJ1dHRvbicpLmNzcyh7ZGlzcGxheTogXCJub25lXCJ9KTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTZXRzIHRoZSBzdGF0ZSBvZiB0aGUgYnV0dG9uLiBUaGUgYnV0dG9uIGhhcyBibHVlIGdsb3cgaWYgZGVza3RvcFxuICAgICAqIHN0cmVhbWluZyBpcyBhY3RpdmUuXG4gICAgICogQHBhcmFtIGFjdGl2ZSB0aGUgc3RhdGUgb2YgdGhlIGRlc2t0b3Agc3RyZWFtaW5nLlxuICAgICAqL1xuICAgIG15LmNoYW5nZURlc2t0b3BTaGFyaW5nQnV0dG9uU3RhdGUgPSBmdW5jdGlvbiAoYWN0aXZlKSB7XG4gICAgICAgIHZhciBidXR0b24gPSAkKFwiI2Rlc2t0b3BzaGFyaW5nID4gYVwiKTtcbiAgICAgICAgaWYgKGFjdGl2ZSlcbiAgICAgICAge1xuICAgICAgICAgICAgYnV0dG9uLmFkZENsYXNzKFwiZ2xvd1wiKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlXG4gICAgICAgIHtcbiAgICAgICAgICAgIGJ1dHRvbi5yZW1vdmVDbGFzcyhcImdsb3dcIik7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgcmV0dXJuIG15O1xufShUb29sYmFyIHx8IHt9KSk7XG5cbm1vZHVsZS5leHBvcnRzID0gVG9vbGJhcjsiLCJ2YXIgSml0c2lQb3BvdmVyID0gKGZ1bmN0aW9uICgpIHtcbiAgICAvKipcbiAgICAgKiBDb25zdHJ1Y3RzIG5ldyBKaXRzaVBvcG92ZXIgYW5kIGF0dGFjaGVzIGl0IHRvIHRoZSBlbGVtZW50XG4gICAgICogQHBhcmFtIGVsZW1lbnQganF1ZXJ5IHNlbGVjdG9yXG4gICAgICogQHBhcmFtIG9wdGlvbnMgdGhlIG9wdGlvbnMgZm9yIHRoZSBwb3BvdmVyLlxuICAgICAqIEBjb25zdHJ1Y3RvclxuICAgICAqL1xuICAgIGZ1bmN0aW9uIEppdHNpUG9wb3ZlcihlbGVtZW50LCBvcHRpb25zKVxuICAgIHtcbiAgICAgICAgdGhpcy5vcHRpb25zID0ge1xuICAgICAgICAgICAgc2tpbjogXCJ3aGl0ZVwiLFxuICAgICAgICAgICAgY29udGVudDogXCJcIlxuICAgICAgICB9O1xuICAgICAgICBpZihvcHRpb25zKVxuICAgICAgICB7XG4gICAgICAgICAgICBpZihvcHRpb25zLnNraW4pXG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnNraW4gPSBvcHRpb25zLnNraW47XG5cbiAgICAgICAgICAgIGlmKG9wdGlvbnMuY29udGVudClcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuY29udGVudCA9IG9wdGlvbnMuY29udGVudDtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuZWxlbWVudElzSG92ZXJlZCA9IGZhbHNlO1xuICAgICAgICB0aGlzLnBvcG92ZXJJc0hvdmVyZWQgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5wb3BvdmVyU2hvd24gPSBmYWxzZTtcblxuICAgICAgICBlbGVtZW50LmRhdGEoXCJqaXRzaV9wb3BvdmVyXCIsIHRoaXMpO1xuICAgICAgICB0aGlzLmVsZW1lbnQgPSBlbGVtZW50O1xuICAgICAgICB0aGlzLnRlbXBsYXRlID0gJyA8ZGl2IGNsYXNzPVwiaml0c2lwb3BvdmVyICcgKyB0aGlzLm9wdGlvbnMuc2tpbiArXG4gICAgICAgICAgICAnXCI+PGRpdiBjbGFzcz1cImFycm93XCI+PC9kaXY+PGRpdiBjbGFzcz1cImppdHNpcG9wb3Zlci1jb250ZW50XCI+PC9kaXY+JyArXG4gICAgICAgICAgICAnPGRpdiBjbGFzcz1cImppdHNpUG9wdXBtZW51UGFkZGluZ1wiPjwvZGl2PjwvZGl2Pic7XG4gICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgdGhpcy5lbGVtZW50Lm9uKFwibW91c2VlbnRlclwiLCBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBzZWxmLmVsZW1lbnRJc0hvdmVyZWQgPSB0cnVlO1xuICAgICAgICAgICAgc2VsZi5zaG93KCk7XG4gICAgICAgIH0pLm9uKFwibW91c2VsZWF2ZVwiLCBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBzZWxmLmVsZW1lbnRJc0hvdmVyZWQgPSBmYWxzZTtcbiAgICAgICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIHNlbGYuaGlkZSgpO1xuICAgICAgICAgICAgfSwgMTApO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTaG93cyB0aGUgcG9wb3ZlclxuICAgICAqL1xuICAgIEppdHNpUG9wb3Zlci5wcm90b3R5cGUuc2hvdyA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdGhpcy5jcmVhdGVQb3BvdmVyKCk7XG4gICAgICAgIHRoaXMucG9wb3ZlclNob3duID0gdHJ1ZTtcblxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBIaWRlcyB0aGUgcG9wb3ZlclxuICAgICAqL1xuICAgIEppdHNpUG9wb3Zlci5wcm90b3R5cGUuaGlkZSA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgaWYoIXRoaXMuZWxlbWVudElzSG92ZXJlZCAmJiAhdGhpcy5wb3BvdmVySXNIb3ZlcmVkICYmIHRoaXMucG9wb3ZlclNob3duKVxuICAgICAgICB7XG4gICAgICAgICAgICB0aGlzLmZvcmNlSGlkZSgpO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIEhpZGVzIHRoZSBwb3BvdmVyXG4gICAgICovXG4gICAgSml0c2lQb3BvdmVyLnByb3RvdHlwZS5mb3JjZUhpZGUgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICQoXCIuaml0c2lwb3BvdmVyXCIpLnJlbW92ZSgpO1xuICAgICAgICB0aGlzLnBvcG92ZXJTaG93biA9IGZhbHNlO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIHRoZSBwb3BvdmVyIGh0bWxcbiAgICAgKi9cbiAgICBKaXRzaVBvcG92ZXIucHJvdG90eXBlLmNyZWF0ZVBvcG92ZXIgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICQoXCJib2R5XCIpLmFwcGVuZCh0aGlzLnRlbXBsYXRlKTtcbiAgICAgICAgJChcIi5qaXRzaXBvcG92ZXIgPiAuaml0c2lwb3BvdmVyLWNvbnRlbnRcIikuaHRtbCh0aGlzLm9wdGlvbnMuY29udGVudCk7XG4gICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgJChcIi5qaXRzaXBvcG92ZXJcIikub24oXCJtb3VzZWVudGVyXCIsIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHNlbGYucG9wb3ZlcklzSG92ZXJlZCA9IHRydWU7XG4gICAgICAgIH0pLm9uKFwibW91c2VsZWF2ZVwiLCBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBzZWxmLnBvcG92ZXJJc0hvdmVyZWQgPSBmYWxzZTtcbiAgICAgICAgICAgIHNlbGYuaGlkZSgpO1xuICAgICAgICB9KTtcblxuICAgICAgICB0aGlzLnJlZnJlc2hQb3NpdGlvbigpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBSZWZyZXNoZXMgdGhlIHBvc2l0aW9uIG9mIHRoZSBwb3BvdmVyXG4gICAgICovXG4gICAgSml0c2lQb3BvdmVyLnByb3RvdHlwZS5yZWZyZXNoUG9zaXRpb24gPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICQoXCIuaml0c2lwb3BvdmVyXCIpLnBvc2l0aW9uKHtcbiAgICAgICAgICAgIG15OiBcImJvdHRvbVwiLFxuICAgICAgICAgICAgYXQ6IFwidG9wXCIsXG4gICAgICAgICAgICBjb2xsaXNpb246IFwiZml0XCIsXG4gICAgICAgICAgICBvZjogdGhpcy5lbGVtZW50LFxuICAgICAgICAgICAgdXNpbmc6IGZ1bmN0aW9uIChwb3NpdGlvbiwgZWxlbWVudHMpIHtcbiAgICAgICAgICAgICAgICB2YXIgY2FsY0xlZnQgPSBlbGVtZW50cy50YXJnZXQubGVmdCAtIGVsZW1lbnRzLmVsZW1lbnQubGVmdCArIGVsZW1lbnRzLnRhcmdldC53aWR0aC8yO1xuICAgICAgICAgICAgICAgICQoXCIuaml0c2lwb3BvdmVyXCIpLmNzcyh7dG9wOiBwb3NpdGlvbi50b3AsIGxlZnQ6IHBvc2l0aW9uLmxlZnQsIGRpc3BsYXk6IFwidGFibGVcIn0pO1xuICAgICAgICAgICAgICAgICQoXCIuaml0c2lwb3BvdmVyID4gLmFycm93XCIpLmNzcyh7bGVmdDogY2FsY0xlZnR9KTtcbiAgICAgICAgICAgICAgICAkKFwiLmppdHNpcG9wb3ZlciA+IC5qaXRzaVBvcHVwbWVudVBhZGRpbmdcIikuY3NzKHtsZWZ0OiBjYWxjTGVmdCAtIDUwfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBVcGRhdGVzIHRoZSBjb250ZW50IG9mIHBvcG92ZXIuXG4gICAgICogQHBhcmFtIGNvbnRlbnQgbmV3IGNvbnRlbnRcbiAgICAgKi9cbiAgICBKaXRzaVBvcG92ZXIucHJvdG90eXBlLnVwZGF0ZUNvbnRlbnQgPSBmdW5jdGlvbiAoY29udGVudCkge1xuICAgICAgICB0aGlzLm9wdGlvbnMuY29udGVudCA9IGNvbnRlbnQ7XG4gICAgICAgIGlmKCF0aGlzLnBvcG92ZXJTaG93bilcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgJChcIi5qaXRzaXBvcG92ZXJcIikucmVtb3ZlKCk7XG4gICAgICAgIHRoaXMuY3JlYXRlUG9wb3ZlcigpO1xuICAgIH07XG5cbiAgICByZXR1cm4gSml0c2lQb3BvdmVyO1xuXG5cbn0pKCk7XG5cbm1vZHVsZS5leHBvcnRzID0gSml0c2lQb3BvdmVyOyIsIi8qIGdsb2JhbCAkLCBqUXVlcnkgKi9cbnZhciBtZXNzYWdlSGFuZGxlciA9IChmdW5jdGlvbihteSkge1xuXG4gICAgLyoqXG4gICAgICogU2hvd3MgYSBtZXNzYWdlIHRvIHRoZSB1c2VyLlxuICAgICAqXG4gICAgICogQHBhcmFtIHRpdGxlU3RyaW5nIHRoZSB0aXRsZSBvZiB0aGUgbWVzc2FnZVxuICAgICAqIEBwYXJhbSBtZXNzYWdlU3RyaW5nIHRoZSB0ZXh0IG9mIHRoZSBtZXNzYWdlXG4gICAgICovXG4gICAgbXkub3Blbk1lc3NhZ2VEaWFsb2cgPSBmdW5jdGlvbih0aXRsZVN0cmluZywgbWVzc2FnZVN0cmluZykge1xuICAgICAgICAkLnByb21wdChtZXNzYWdlU3RyaW5nLFxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHRpdGxlOiB0aXRsZVN0cmluZyxcbiAgICAgICAgICAgICAgICBwZXJzaXN0ZW50OiBmYWxzZVxuICAgICAgICAgICAgfVxuICAgICAgICApO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTaG93cyBhIG1lc3NhZ2UgdG8gdGhlIHVzZXIgd2l0aCB0d28gYnV0dG9uczogZmlyc3QgaXMgZ2l2ZW4gYXMgYSBwYXJhbWV0ZXIgYW5kIHRoZSBzZWNvbmQgaXMgQ2FuY2VsLlxuICAgICAqXG4gICAgICogQHBhcmFtIHRpdGxlU3RyaW5nIHRoZSB0aXRsZSBvZiB0aGUgbWVzc2FnZVxuICAgICAqIEBwYXJhbSBtc2dTdHJpbmcgdGhlIHRleHQgb2YgdGhlIG1lc3NhZ2VcbiAgICAgKiBAcGFyYW0gcGVyc2lzdGVudCBib29sZWFuIHZhbHVlIHdoaWNoIGRldGVybWluZXMgd2hldGhlciB0aGUgbWVzc2FnZSBpcyBwZXJzaXN0ZW50IG9yIG5vdFxuICAgICAqIEBwYXJhbSBsZWZ0QnV0dG9uIHRoZSBmaXN0IGJ1dHRvbidzIHRleHRcbiAgICAgKiBAcGFyYW0gc3VibWl0RnVuY3Rpb24gZnVuY3Rpb24gdG8gYmUgY2FsbGVkIG9uIHN1Ym1pdFxuICAgICAqIEBwYXJhbSBsb2FkZWRGdW5jdGlvbiBmdW5jdGlvbiB0byBiZSBjYWxsZWQgYWZ0ZXIgdGhlIHByb21wdCBpcyBmdWxseSBsb2FkZWRcbiAgICAgKiBAcGFyYW0gY2xvc2VGdW5jdGlvbiBmdW5jdGlvbiB0byBiZSBjYWxsZWQgYWZ0ZXIgdGhlIHByb21wdCBpcyBjbG9zZWRcbiAgICAgKi9cbiAgICBteS5vcGVuVHdvQnV0dG9uRGlhbG9nID0gZnVuY3Rpb24odGl0bGVTdHJpbmcsIG1zZ1N0cmluZywgcGVyc2lzdGVudCwgbGVmdEJ1dHRvbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VibWl0RnVuY3Rpb24sIGxvYWRlZEZ1bmN0aW9uLCBjbG9zZUZ1bmN0aW9uKSB7XG4gICAgICAgIHZhciBidXR0b25zID0ge307XG4gICAgICAgIGJ1dHRvbnNbbGVmdEJ1dHRvbl0gPSB0cnVlO1xuICAgICAgICBidXR0b25zLkNhbmNlbCA9IGZhbHNlO1xuICAgICAgICAkLnByb21wdChtc2dTdHJpbmcsIHtcbiAgICAgICAgICAgIHRpdGxlOiB0aXRsZVN0cmluZyxcbiAgICAgICAgICAgIHBlcnNpc3RlbnQ6IGZhbHNlLFxuICAgICAgICAgICAgYnV0dG9uczogYnV0dG9ucyxcbiAgICAgICAgICAgIGRlZmF1bHRCdXR0b246IDEsXG4gICAgICAgICAgICBsb2FkZWQ6IGxvYWRlZEZ1bmN0aW9uLFxuICAgICAgICAgICAgc3VibWl0OiBzdWJtaXRGdW5jdGlvbixcbiAgICAgICAgICAgIGNsb3NlOiBjbG9zZUZ1bmN0aW9uXG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTaG93cyBhIG1lc3NhZ2UgdG8gdGhlIHVzZXIgd2l0aCB0d28gYnV0dG9uczogZmlyc3QgaXMgZ2l2ZW4gYXMgYSBwYXJhbWV0ZXIgYW5kIHRoZSBzZWNvbmQgaXMgQ2FuY2VsLlxuICAgICAqXG4gICAgICogQHBhcmFtIHRpdGxlU3RyaW5nIHRoZSB0aXRsZSBvZiB0aGUgbWVzc2FnZVxuICAgICAqIEBwYXJhbSBtc2dTdHJpbmcgdGhlIHRleHQgb2YgdGhlIG1lc3NhZ2VcbiAgICAgKiBAcGFyYW0gcGVyc2lzdGVudCBib29sZWFuIHZhbHVlIHdoaWNoIGRldGVybWluZXMgd2hldGhlciB0aGUgbWVzc2FnZSBpcyBwZXJzaXN0ZW50IG9yIG5vdFxuICAgICAqIEBwYXJhbSBidXR0b25zIG9iamVjdCB3aXRoIHRoZSBidXR0b25zLiBUaGUga2V5cyBtdXN0IGJlIHRoZSBuYW1lIG9mIHRoZSBidXR0b24gYW5kIHZhbHVlIGlzIHRoZSB2YWx1ZVxuICAgICAqIHRoYXQgd2lsbCBiZSBwYXNzZWQgdG8gc3VibWl0RnVuY3Rpb25cbiAgICAgKiBAcGFyYW0gc3VibWl0RnVuY3Rpb24gZnVuY3Rpb24gdG8gYmUgY2FsbGVkIG9uIHN1Ym1pdFxuICAgICAqIEBwYXJhbSBsb2FkZWRGdW5jdGlvbiBmdW5jdGlvbiB0byBiZSBjYWxsZWQgYWZ0ZXIgdGhlIHByb21wdCBpcyBmdWxseSBsb2FkZWRcbiAgICAgKi9cbiAgICBteS5vcGVuRGlhbG9nID0gZnVuY3Rpb24gKHRpdGxlU3RyaW5nLCAgICBtc2dTdHJpbmcsIHBlcnNpc3RlbnQsIGJ1dHRvbnMsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJtaXRGdW5jdGlvbiwgbG9hZGVkRnVuY3Rpb24pIHtcbiAgICAgICAgdmFyIGFyZ3MgPSB7XG4gICAgICAgICAgICB0aXRsZTogdGl0bGVTdHJpbmcsXG4gICAgICAgICAgICBwZXJzaXN0ZW50OiBwZXJzaXN0ZW50LFxuICAgICAgICAgICAgYnV0dG9uczogYnV0dG9ucyxcbiAgICAgICAgICAgIGRlZmF1bHRCdXR0b246IDEsXG4gICAgICAgICAgICBsb2FkZWQ6IGxvYWRlZEZ1bmN0aW9uLFxuICAgICAgICAgICAgc3VibWl0OiBzdWJtaXRGdW5jdGlvblxuICAgICAgICB9O1xuICAgICAgICBpZiAocGVyc2lzdGVudCkge1xuICAgICAgICAgICAgYXJncy5jbG9zZVRleHQgPSAnJztcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gJC5wcm9tcHQobXNnU3RyaW5nLCBhcmdzKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogQ2xvc2VzIGN1cnJlbnRseSBvcGVuZWQgZGlhbG9nLlxuICAgICAqL1xuICAgIG15LmNsb3NlRGlhbG9nID0gZnVuY3Rpb24gKCkge1xuICAgICAgICAkLnByb21wdC5jbG9zZSgpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTaG93cyBhIGRpYWxvZyB3aXRoIGRpZmZlcmVudCBzdGF0ZXMgdG8gdGhlIHVzZXIuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gc3RhdGVzT2JqZWN0IG9iamVjdCBjb250YWluaW5nIGFsbCB0aGUgc3RhdGVzIG9mIHRoZSBkaWFsb2dcbiAgICAgKiBAcGFyYW0gbG9hZGVkRnVuY3Rpb24gZnVuY3Rpb24gdG8gYmUgY2FsbGVkIGFmdGVyIHRoZSBwcm9tcHQgaXMgZnVsbHkgbG9hZGVkXG4gICAgICogQHBhcmFtIHN0YXRlQ2hhbmdlZEZ1bmN0aW9uIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCB3aGVuIHRoZSBzdGF0ZSBvZiB0aGUgZGlhbG9nIGlzIGNoYW5nZWRcbiAgICAgKi9cbiAgICBteS5vcGVuRGlhbG9nV2l0aFN0YXRlcyA9IGZ1bmN0aW9uKHN0YXRlc09iamVjdCwgbG9hZGVkRnVuY3Rpb24sIHN0YXRlQ2hhbmdlZEZ1bmN0aW9uKSB7XG5cblxuICAgICAgICB2YXIgbXlQcm9tcHQgPSAkLnByb21wdChzdGF0ZXNPYmplY3QpO1xuXG4gICAgICAgIG15UHJvbXB0Lm9uKCdpbXByb21wdHU6bG9hZGVkJywgbG9hZGVkRnVuY3Rpb24pO1xuICAgICAgICBteVByb21wdC5vbignaW1wcm9tcHR1OnN0YXRlY2hhbmdlZCcsIHN0YXRlQ2hhbmdlZEZ1bmN0aW9uKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogT3BlbnMgbmV3IHBvcHVwIHdpbmRvdyBmb3IgZ2l2ZW4gPHR0PnVybDwvdHQ+IGNlbnRlcmVkIG92ZXIgY3VycmVudFxuICAgICAqIHdpbmRvdy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB1cmwgdGhlIFVSTCB0byBiZSBkaXNwbGF5ZWQgaW4gdGhlIHBvcHVwIHdpbmRvd1xuICAgICAqIEBwYXJhbSB3IHRoZSB3aWR0aCBvZiB0aGUgcG9wdXAgd2luZG93XG4gICAgICogQHBhcmFtIGggdGhlIGhlaWdodCBvZiB0aGUgcG9wdXAgd2luZG93XG4gICAgICogQHBhcmFtIG9uUG9wdXBDbG9zZWQgb3B0aW9uYWwgY2FsbGJhY2sgZnVuY3Rpb24gY2FsbGVkIHdoZW4gcG9wdXAgd2luZG93XG4gICAgICogICAgICAgIGhhcyBiZWVuIGNsb3NlZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIHBvcHVwIHdpbmRvdyBvYmplY3QgaWYgb3BlbmVkIHN1Y2Nlc3NmdWxseSBvciB1bmRlZmluZWRcbiAgICAgKiAgICAgICAgICBpbiBjYXNlIHdlIGZhaWxlZCB0byBvcGVuIGl0KHBvcHVwIGJsb2NrZWQpXG4gICAgICovXG4gICAgbXkub3BlbkNlbnRlcmVkUG9wdXAgPSBmdW5jdGlvbiAodXJsLCB3LCBoLCBvblBvcHVwQ2xvc2VkKSB7XG4gICAgICAgIHZhciBsID0gd2luZG93LnNjcmVlblggKyAod2luZG93LmlubmVyV2lkdGggLyAyKSAtICh3IC8gMik7XG4gICAgICAgIHZhciB0ID0gd2luZG93LnNjcmVlblkgKyAod2luZG93LmlubmVySGVpZ2h0IC8gMikgLSAoaCAvIDIpO1xuICAgICAgICB2YXIgcG9wdXAgPSB3aW5kb3cub3BlbihcbiAgICAgICAgICAgIHVybCwgJ19ibGFuaycsXG4gICAgICAgICAgICAndG9wPScgKyB0ICsgJywgbGVmdD0nICsgbCArICcsIHdpZHRoPScgKyB3ICsgJywgaGVpZ2h0PScgKyBoICsgJycpO1xuICAgICAgICBpZiAocG9wdXAgJiYgb25Qb3B1cENsb3NlZCkge1xuICAgICAgICAgICAgdmFyIHBvbGxUaW1lciA9IHdpbmRvdy5zZXRJbnRlcnZhbChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgaWYgKHBvcHVwLmNsb3NlZCAhPT0gZmFsc2UpIHtcbiAgICAgICAgICAgICAgICAgICAgd2luZG93LmNsZWFySW50ZXJ2YWwocG9sbFRpbWVyKTtcbiAgICAgICAgICAgICAgICAgICAgb25Qb3B1cENsb3NlZCgpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sIDIwMCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHBvcHVwO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTaG93cyBhIGRpYWxvZyBwcm9tcHRpbmcgdGhlIHVzZXIgdG8gc2VuZCBhbiBlcnJvciByZXBvcnQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gdGl0bGVTdHJpbmcgdGhlIHRpdGxlIG9mIHRoZSBtZXNzYWdlXG4gICAgICogQHBhcmFtIG1zZ1N0cmluZyB0aGUgdGV4dCBvZiB0aGUgbWVzc2FnZVxuICAgICAqIEBwYXJhbSBlcnJvciB0aGUgZXJyb3IgdGhhdCBpcyBiZWluZyByZXBvcnRlZFxuICAgICAqL1xuICAgIG15Lm9wZW5SZXBvcnREaWFsb2cgPSBmdW5jdGlvbih0aXRsZVN0cmluZywgbXNnU3RyaW5nLCBlcnJvcikge1xuICAgICAgICBteS5vcGVuTWVzc2FnZURpYWxvZyh0aXRsZVN0cmluZywgbXNnU3RyaW5nKTtcbiAgICAgICAgY29uc29sZS5sb2coZXJyb3IpO1xuICAgICAgICAvL0ZJWE1FIHNlbmQgdGhlIGVycm9yIHRvIHRoZSBzZXJ2ZXJcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogIFNob3dzIGFuIGVycm9yIGRpYWxvZyB0byB0aGUgdXNlci5cbiAgICAgKiBAcGFyYW0gdGl0bGUgdGhlIHRpdGxlIG9mIHRoZSBtZXNzYWdlXG4gICAgICogQHBhcmFtIG1lc3NhZ2UgdGhlIHRleHQgb2YgdGhlIG1lc3NhZmVcbiAgICAgKi9cbiAgICBteS5zaG93RXJyb3IgPSBmdW5jdGlvbih0aXRsZSwgbWVzc2FnZSkge1xuICAgICAgICBpZighKHRpdGxlIHx8IG1lc3NhZ2UpKSB7XG4gICAgICAgICAgICB0aXRsZSA9IHRpdGxlIHx8IFwiT29wcyFcIjtcbiAgICAgICAgICAgIG1lc3NhZ2UgPSBtZXNzYWdlIHx8IFwiVGhlcmUgd2FzIHNvbWUga2luZCBvZiBlcnJvclwiO1xuICAgICAgICB9XG4gICAgICAgIG1lc3NhZ2VIYW5kbGVyLm9wZW5NZXNzYWdlRGlhbG9nKHRpdGxlLCBtZXNzYWdlKTtcbiAgICB9O1xuXG4gICAgbXkubm90aWZ5ID0gZnVuY3Rpb24oZGlzcGxheU5hbWUsIGNscywgbWVzc2FnZSkge1xuICAgICAgICB0b2FzdHIuaW5mbyhcbiAgICAgICAgICAgICc8c3BhbiBjbGFzcz1cIm5pY2tuYW1lXCI+JyArXG4gICAgICAgICAgICAgICAgZGlzcGxheU5hbWUgK1xuICAgICAgICAgICAgJzwvc3Bhbj48YnI+JyArXG4gICAgICAgICAgICAnPHNwYW4gY2xhc3M9JyArIGNscyArICc+JyArXG4gICAgICAgICAgICAgICAgbWVzc2FnZSArXG4gICAgICAgICAgICAnPC9zcGFuPicpO1xuICAgIH07XG5cbiAgICByZXR1cm4gbXk7XG59KG1lc3NhZ2VIYW5kbGVyIHx8IHt9KSk7XG5cbm1vZHVsZS5leHBvcnRzID0gbWVzc2FnZUhhbmRsZXI7XG5cblxuIiwiLyoqXG4gKiBDcmVhdGVkIGJ5IGhyaXN0byBvbiAxMi8yMi8xNC5cbiAqL1xubW9kdWxlLmV4cG9ydHMgPSB7XG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgYXZhaWxhYmxlIHZpZGVvIHdpZHRoLlxuICAgICAqL1xuICAgIGdldEF2YWlsYWJsZVZpZGVvV2lkdGg6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIFBhbmVsVG9nZ2xlciA9IHJlcXVpcmUoXCIuLi9zaWRlX3Bhbm5lbHMvU2lkZVBhbmVsVG9nZ2xlclwiKTtcbiAgICAgICAgdmFyIHJpZ2h0UGFuZWxXaWR0aFxuICAgICAgICAgICAgPSBQYW5lbFRvZ2dsZXIuaXNWaXNpYmxlKCkgPyBQYW5lbFRvZ2dsZXIuZ2V0UGFuZWxTaXplKClbMF0gOiAwO1xuXG4gICAgICAgIHJldHVybiB3aW5kb3cuaW5uZXJXaWR0aCAtIHJpZ2h0UGFuZWxXaWR0aDtcbiAgICB9LFxuICAgIC8qKlxuICAgICAqIENoYW5nZXMgdGhlIHN0eWxlIGNsYXNzIG9mIHRoZSBlbGVtZW50IGdpdmVuIGJ5IGlkLlxuICAgICAqL1xuICAgIGJ1dHRvbkNsaWNrOiBmdW5jdGlvbihpZCwgY2xhc3NuYW1lKSB7XG4gICAgICAgICQoaWQpLnRvZ2dsZUNsYXNzKGNsYXNzbmFtZSk7IC8vIGFkZCB0aGUgY2xhc3MgdG8gdGhlIGNsaWNrZWQgZWxlbWVudFxuICAgIH1cblxuXG59OyIsInZhciBKaXRzaVBvcG92ZXIgPSByZXF1aXJlKFwiLi4vdXRpbC9KaXRzaVBvcG92ZXJcIik7XG5cbi8qKlxuICogQ29uc3RydWN0cyBuZXcgY29ubmVjdGlvbiBpbmRpY2F0b3IuXG4gKiBAcGFyYW0gdmlkZW9Db250YWluZXIgdGhlIHZpZGVvIGNvbnRhaW5lciBhc3NvY2lhdGVkIHdpdGggdGhlIGluZGljYXRvci5cbiAqIEBjb25zdHJ1Y3RvclxuICovXG5mdW5jdGlvbiBDb25uZWN0aW9uSW5kaWNhdG9yKHZpZGVvQ29udGFpbmVyLCBqaWQsIFZpZGVvTGF5b3V0KVxue1xuICAgIHRoaXMudmlkZW9Db250YWluZXIgPSB2aWRlb0NvbnRhaW5lcjtcbiAgICB0aGlzLmJhbmR3aWR0aCA9IG51bGw7XG4gICAgdGhpcy5wYWNrZXRMb3NzID0gbnVsbDtcbiAgICB0aGlzLmJpdHJhdGUgPSBudWxsO1xuICAgIHRoaXMuc2hvd01vcmVWYWx1ZSA9IGZhbHNlO1xuICAgIHRoaXMucmVzb2x1dGlvbiA9IG51bGw7XG4gICAgdGhpcy50cmFuc3BvcnQgPSBbXTtcbiAgICB0aGlzLnBvcG92ZXIgPSBudWxsO1xuICAgIHRoaXMuamlkID0gamlkO1xuICAgIHRoaXMuY3JlYXRlKCk7XG4gICAgdGhpcy52aWRlb0xheW91dCA9IFZpZGVvTGF5b3V0O1xufVxuXG4vKipcbiAqIFZhbHVlcyBmb3IgdGhlIGNvbm5lY3Rpb24gcXVhbGl0eVxuICogQHR5cGUge3s5ODogc3RyaW5nLFxuICogICAgICAgICA4MTogc3RyaW5nLFxuICogICAgICAgICA2NDogc3RyaW5nLFxuICogICAgICAgICA0Nzogc3RyaW5nLFxuICogICAgICAgICAzMDogc3RyaW5nLFxuICogICAgICAgICAwOiBzdHJpbmd9fVxuICovXG5Db25uZWN0aW9uSW5kaWNhdG9yLmNvbm5lY3Rpb25RdWFsaXR5VmFsdWVzID0ge1xuICAgIDk4OiBcIjE4cHhcIiwgLy9mdWxsXG4gICAgODE6IFwiMTVweFwiLC8vNCBiYXJzXG4gICAgNjQ6IFwiMTFweFwiLC8vMyBiYXJzXG4gICAgNDc6IFwiN3B4XCIsLy8yIGJhcnNcbiAgICAzMDogXCIzcHhcIiwvLzEgYmFyXG4gICAgMDogXCIwcHhcIi8vZW1wdHlcbn07XG5cbkNvbm5lY3Rpb25JbmRpY2F0b3IuZ2V0SVAgPSBmdW5jdGlvbih2YWx1ZSlcbntcbiAgICByZXR1cm4gdmFsdWUuc3Vic3RyaW5nKDAsIHZhbHVlLmxhc3RJbmRleE9mKFwiOlwiKSk7XG59O1xuXG5Db25uZWN0aW9uSW5kaWNhdG9yLmdldFBvcnQgPSBmdW5jdGlvbih2YWx1ZSlcbntcbiAgICByZXR1cm4gdmFsdWUuc3Vic3RyaW5nKHZhbHVlLmxhc3RJbmRleE9mKFwiOlwiKSArIDEsIHZhbHVlLmxlbmd0aCk7XG59O1xuXG5Db25uZWN0aW9uSW5kaWNhdG9yLmdldFN0cmluZ0Zyb21BcnJheSA9IGZ1bmN0aW9uIChhcnJheSkge1xuICAgIHZhciByZXMgPSBcIlwiO1xuICAgIGZvcih2YXIgaSA9IDA7IGkgPCBhcnJheS5sZW5ndGg7IGkrKylcbiAgICB7XG4gICAgICAgIHJlcyArPSAoaSA9PT0gMD8gXCJcIiA6IFwiLCBcIikgKyBhcnJheVtpXTtcbiAgICB9XG4gICAgcmV0dXJuIHJlcztcbn07XG5cbi8qKlxuICogR2VuZXJhdGVzIHRoZSBodG1sIGNvbnRlbnQuXG4gKiBAcmV0dXJucyB7c3RyaW5nfSB0aGUgaHRtbCBjb250ZW50LlxuICovXG5Db25uZWN0aW9uSW5kaWNhdG9yLnByb3RvdHlwZS5nZW5lcmF0ZVRleHQgPSBmdW5jdGlvbiAoKSB7XG4gICAgdmFyIGRvd25sb2FkQml0cmF0ZSwgdXBsb2FkQml0cmF0ZSwgcGFja2V0TG9zcywgcmVzb2x1dGlvbiwgaTtcblxuICAgIGlmKHRoaXMuYml0cmF0ZSA9PT0gbnVsbClcbiAgICB7XG4gICAgICAgIGRvd25sb2FkQml0cmF0ZSA9IFwiTi9BXCI7XG4gICAgICAgIHVwbG9hZEJpdHJhdGUgPSBcIk4vQVwiO1xuICAgIH1cbiAgICBlbHNlXG4gICAge1xuICAgICAgICBkb3dubG9hZEJpdHJhdGUgPVxuICAgICAgICAgICAgdGhpcy5iaXRyYXRlLmRvd25sb2FkPyB0aGlzLmJpdHJhdGUuZG93bmxvYWQgKyBcIiBLYnBzXCIgOiBcIk4vQVwiO1xuICAgICAgICB1cGxvYWRCaXRyYXRlID1cbiAgICAgICAgICAgIHRoaXMuYml0cmF0ZS51cGxvYWQ/IHRoaXMuYml0cmF0ZS51cGxvYWQgKyBcIiBLYnBzXCIgOiBcIk4vQVwiO1xuICAgIH1cblxuICAgIGlmKHRoaXMucGFja2V0TG9zcyA9PT0gbnVsbClcbiAgICB7XG4gICAgICAgIHBhY2tldExvc3MgPSBcIk4vQVwiO1xuICAgIH1cbiAgICBlbHNlXG4gICAge1xuXG4gICAgICAgIHBhY2tldExvc3MgPSBcIjxzcGFuIGNsYXNzPSdqaXRzaXBvcG92ZXJfZ3JlZW4nPiZkYXJyOzwvc3Bhbj5cIiArXG4gICAgICAgICAgICAodGhpcy5wYWNrZXRMb3NzLmRvd25sb2FkICE9PSBudWxsPyB0aGlzLnBhY2tldExvc3MuZG93bmxvYWQgOiBcIk4vQVwiKSArXG4gICAgICAgICAgICBcIiUgPHNwYW4gY2xhc3M9J2ppdHNpcG9wb3Zlcl9vcmFuZ2UnPiZ1YXJyOzwvc3Bhbj5cIiArXG4gICAgICAgICAgICAodGhpcy5wYWNrZXRMb3NzLnVwbG9hZCAhPT0gbnVsbD8gdGhpcy5wYWNrZXRMb3NzLnVwbG9hZCA6IFwiTi9BXCIpICsgXCIlXCI7XG4gICAgfVxuXG4gICAgdmFyIHJlc29sdXRpb25WYWx1ZSA9IG51bGw7XG4gICAgaWYodGhpcy5yZXNvbHV0aW9uICYmIHRoaXMuamlkICE9IG51bGwpXG4gICAge1xuICAgICAgICB2YXIga2V5cyA9IE9iamVjdC5rZXlzKHRoaXMucmVzb2x1dGlvbik7XG4gICAgICAgIGlmKGtleXMubGVuZ3RoID09IDEpXG4gICAgICAgIHtcbiAgICAgICAgICAgIGZvcih2YXIgc3NyYyBpbiB0aGlzLnJlc29sdXRpb24pXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgcmVzb2x1dGlvblZhbHVlID0gdGhpcy5yZXNvbHV0aW9uW3NzcmNdO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYoa2V5cy5sZW5ndGggPiAxKVxuICAgICAgICB7XG4gICAgICAgICAgICB2YXIgZGlzcGxheWVkU3NyYyA9IHNpbXVsY2FzdC5nZXRSZWNlaXZpbmdTU1JDKHRoaXMuamlkKTtcbiAgICAgICAgICAgIHJlc29sdXRpb25WYWx1ZSA9IHRoaXMucmVzb2x1dGlvbltkaXNwbGF5ZWRTc3JjXTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGlmKHRoaXMuamlkID09PSBudWxsKVxuICAgIHtcbiAgICAgICAgcmVzb2x1dGlvbiA9IFwiXCI7XG4gICAgICAgIGlmKHRoaXMucmVzb2x1dGlvbiA9PT0gbnVsbCB8fCAhT2JqZWN0LmtleXModGhpcy5yZXNvbHV0aW9uKSB8fFxuICAgICAgICAgICAgT2JqZWN0LmtleXModGhpcy5yZXNvbHV0aW9uKS5sZW5ndGggPT09IDApXG4gICAgICAgIHtcbiAgICAgICAgICAgIHJlc29sdXRpb24gPSBcIk4vQVwiO1xuICAgICAgICB9XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIGZvcihpIGluIHRoaXMucmVzb2x1dGlvbilcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICByZXNvbHV0aW9uVmFsdWUgPSB0aGlzLnJlc29sdXRpb25baV07XG4gICAgICAgICAgICAgICAgaWYocmVzb2x1dGlvblZhbHVlKVxuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgaWYocmVzb2x1dGlvblZhbHVlLmhlaWdodCAmJlxuICAgICAgICAgICAgICAgICAgICAgICAgcmVzb2x1dGlvblZhbHVlLndpZHRoKVxuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXNvbHV0aW9uICs9IChyZXNvbHV0aW9uID09PSBcIlwiPyBcIlwiIDogXCIsIFwiKSArXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzb2x1dGlvblZhbHVlLndpZHRoICsgXCJ4XCIgK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc29sdXRpb25WYWx1ZS5oZWlnaHQ7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgfVxuICAgIGVsc2UgaWYoIXJlc29sdXRpb25WYWx1ZSB8fFxuICAgICAgICAhcmVzb2x1dGlvblZhbHVlLmhlaWdodCB8fFxuICAgICAgICAhcmVzb2x1dGlvblZhbHVlLndpZHRoKVxuICAgIHtcbiAgICAgICAgcmVzb2x1dGlvbiA9IFwiTi9BXCI7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICAgIHJlc29sdXRpb24gPSByZXNvbHV0aW9uVmFsdWUud2lkdGggKyBcInhcIiArIHJlc29sdXRpb25WYWx1ZS5oZWlnaHQ7XG4gICAgfVxuXG4gICAgdmFyIHJlc3VsdCA9IFwiPHRhYmxlIHN0eWxlPSd3aWR0aDoxMDAlJz5cIiArXG4gICAgICAgIFwiPHRyPlwiICtcbiAgICAgICAgXCI8dGQ+PHNwYW4gY2xhc3M9J2ppdHNpcG9wb3Zlcl9ibHVlJz5CaXRyYXRlOjwvc3Bhbj48L3RkPlwiICtcbiAgICAgICAgXCI8dGQ+PHNwYW4gY2xhc3M9J2ppdHNpcG9wb3Zlcl9ncmVlbic+JmRhcnI7PC9zcGFuPlwiICtcbiAgICAgICAgZG93bmxvYWRCaXRyYXRlICsgXCIgPHNwYW4gY2xhc3M9J2ppdHNpcG9wb3Zlcl9vcmFuZ2UnPiZ1YXJyOzwvc3Bhbj5cIiArXG4gICAgICAgIHVwbG9hZEJpdHJhdGUgKyBcIjwvdGQ+XCIgK1xuICAgICAgICBcIjwvdHI+PHRyPlwiICtcbiAgICAgICAgXCI8dGQ+PHNwYW4gY2xhc3M9J2ppdHNpcG9wb3Zlcl9ibHVlJz5QYWNrZXQgbG9zczogPC9zcGFuPjwvdGQ+XCIgK1xuICAgICAgICBcIjx0ZD5cIiArIHBhY2tldExvc3MgICsgXCI8L3RkPlwiICtcbiAgICAgICAgXCI8L3RyPjx0cj5cIiArXG4gICAgICAgIFwiPHRkPjxzcGFuIGNsYXNzPSdqaXRzaXBvcG92ZXJfYmx1ZSc+UmVzb2x1dGlvbjo8L3NwYW4+PC90ZD5cIiArXG4gICAgICAgIFwiPHRkPlwiICsgcmVzb2x1dGlvbiArIFwiPC90ZD48L3RyPjwvdGFibGU+XCI7XG5cbiAgICBpZih0aGlzLnZpZGVvQ29udGFpbmVyLmlkID09IFwibG9jYWxWaWRlb0NvbnRhaW5lclwiKVxuICAgICAgICByZXN1bHQgKz0gXCI8ZGl2IGNsYXNzPVxcXCJqaXRzaXBvcG92ZXJfc2hvd21vcmVcXFwiIFwiICtcbiAgICAgICAgICAgIFwib25jbGljayA9IFxcXCJVSS5jb25uZWN0aW9uSW5kaWNhdG9yU2hvd01vcmUoJ1wiICtcbiAgICAgICAgICAgIHRoaXMudmlkZW9Db250YWluZXIuaWQgKyBcIicpXFxcIj5cIiArXG4gICAgICAgICAgICAodGhpcy5zaG93TW9yZVZhbHVlPyBcIlNob3cgbGVzc1wiIDogXCJTaG93IE1vcmVcIikgKyBcIjwvZGl2PjxiciAvPlwiO1xuXG4gICAgaWYodGhpcy5zaG93TW9yZVZhbHVlKVxuICAgIHtcbiAgICAgICAgdmFyIGRvd25sb2FkQmFuZHdpZHRoLCB1cGxvYWRCYW5kd2lkdGgsIHRyYW5zcG9ydDtcbiAgICAgICAgaWYodGhpcy5iYW5kd2lkdGggPT09IG51bGwpXG4gICAgICAgIHtcbiAgICAgICAgICAgIGRvd25sb2FkQmFuZHdpZHRoID0gXCJOL0FcIjtcbiAgICAgICAgICAgIHVwbG9hZEJhbmR3aWR0aCA9IFwiTi9BXCI7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZVxuICAgICAgICB7XG4gICAgICAgICAgICBkb3dubG9hZEJhbmR3aWR0aCA9IHRoaXMuYmFuZHdpZHRoLmRvd25sb2FkP1xuICAgICAgICAgICAgICAgIHRoaXMuYmFuZHdpZHRoLmRvd25sb2FkICsgXCIgS2Jwc1wiIDpcbiAgICAgICAgICAgICAgICBcIk4vQVwiO1xuICAgICAgICAgICAgdXBsb2FkQmFuZHdpZHRoID0gdGhpcy5iYW5kd2lkdGgudXBsb2FkP1xuICAgICAgICAgICAgICAgIHRoaXMuYmFuZHdpZHRoLnVwbG9hZCArIFwiIEticHNcIiA6XG4gICAgICAgICAgICAgICAgXCJOL0FcIjtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmKCF0aGlzLnRyYW5zcG9ydCB8fCB0aGlzLnRyYW5zcG9ydC5sZW5ndGggPT09IDApXG4gICAgICAgIHtcbiAgICAgICAgICAgIHRyYW5zcG9ydCA9IFwiPHRyPlwiICtcbiAgICAgICAgICAgICAgICBcIjx0ZD48c3BhbiBjbGFzcz0naml0c2lwb3BvdmVyX2JsdWUnPkFkZHJlc3M6PC9zcGFuPjwvdGQ+XCIgK1xuICAgICAgICAgICAgICAgIFwiPHRkPiBOL0E8L3RkPjwvdHI+XCI7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZVxuICAgICAgICB7XG4gICAgICAgICAgICB2YXIgZGF0YSA9IHtyZW1vdGVJUDogW10sIGxvY2FsSVA6W10sIHJlbW90ZVBvcnQ6W10sIGxvY2FsUG9ydDpbXX07XG4gICAgICAgICAgICBmb3IoaSA9IDA7IGkgPCB0aGlzLnRyYW5zcG9ydC5sZW5ndGg7IGkrKylcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICB2YXIgaXAgPSAgQ29ubmVjdGlvbkluZGljYXRvci5nZXRJUCh0aGlzLnRyYW5zcG9ydFtpXS5pcCk7XG4gICAgICAgICAgICAgICAgdmFyIHBvcnQgPSBDb25uZWN0aW9uSW5kaWNhdG9yLmdldFBvcnQodGhpcy50cmFuc3BvcnRbaV0uaXApO1xuICAgICAgICAgICAgICAgIHZhciBsb2NhbElQID1cbiAgICAgICAgICAgICAgICAgICAgQ29ubmVjdGlvbkluZGljYXRvci5nZXRJUCh0aGlzLnRyYW5zcG9ydFtpXS5sb2NhbGlwKTtcbiAgICAgICAgICAgICAgICB2YXIgbG9jYWxQb3J0ID1cbiAgICAgICAgICAgICAgICAgICAgQ29ubmVjdGlvbkluZGljYXRvci5nZXRQb3J0KHRoaXMudHJhbnNwb3J0W2ldLmxvY2FsaXApO1xuICAgICAgICAgICAgICAgIGlmKGRhdGEucmVtb3RlSVAuaW5kZXhPZihpcCkgPT0gLTEpXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICBkYXRhLnJlbW90ZUlQLnB1c2goaXApO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGlmKGRhdGEucmVtb3RlUG9ydC5pbmRleE9mKHBvcnQpID09IC0xKVxuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgZGF0YS5yZW1vdGVQb3J0LnB1c2gocG9ydCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYoZGF0YS5sb2NhbElQLmluZGV4T2YobG9jYWxJUCkgPT0gLTEpXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICBkYXRhLmxvY2FsSVAucHVzaChsb2NhbElQKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZihkYXRhLmxvY2FsUG9ydC5pbmRleE9mKGxvY2FsUG9ydCkgPT0gLTEpXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICBkYXRhLmxvY2FsUG9ydC5wdXNoKGxvY2FsUG9ydCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB2YXIgbG9jYWxUcmFuc3BvcnQgPVxuICAgICAgICAgICAgICAgIFwiPHRyPjx0ZD48c3BhbiBjbGFzcz0naml0c2lwb3BvdmVyX2JsdWUnPkxvY2FsIGFkZHJlc3NcIiArXG4gICAgICAgICAgICAgICAgKGRhdGEubG9jYWxJUC5sZW5ndGggPiAxPyBcImVzXCIgOiBcIlwiKSArIFwiOiA8L3NwYW4+PC90ZD48dGQ+IFwiICtcbiAgICAgICAgICAgICAgICBDb25uZWN0aW9uSW5kaWNhdG9yLmdldFN0cmluZ0Zyb21BcnJheShkYXRhLmxvY2FsSVApICtcbiAgICAgICAgICAgICAgICBcIjwvdGQ+PC90cj5cIjtcbiAgICAgICAgICAgIHRyYW5zcG9ydCA9XG4gICAgICAgICAgICAgICAgXCI8dHI+PHRkPjxzcGFuIGNsYXNzPSdqaXRzaXBvcG92ZXJfYmx1ZSc+UmVtb3RlIGFkZHJlc3NcIitcbiAgICAgICAgICAgICAgICAoZGF0YS5yZW1vdGVJUC5sZW5ndGggPiAxPyBcImVzXCIgOiBcIlwiKSArIFwiOjwvc3Bhbj48L3RkPjx0ZD4gXCIgK1xuICAgICAgICAgICAgICAgIENvbm5lY3Rpb25JbmRpY2F0b3IuZ2V0U3RyaW5nRnJvbUFycmF5KGRhdGEucmVtb3RlSVApICtcbiAgICAgICAgICAgICAgICBcIjwvdGQ+PC90cj5cIjtcbiAgICAgICAgICAgIGlmKHRoaXMudHJhbnNwb3J0Lmxlbmd0aCA+IDEpXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgdHJhbnNwb3J0ICs9IFwiPHRyPlwiICtcbiAgICAgICAgICAgICAgICAgICAgXCI8dGQ+XCIgK1xuICAgICAgICAgICAgICAgICAgICBcIjxzcGFuIGNsYXNzPSdqaXRzaXBvcG92ZXJfYmx1ZSc+UmVtb3RlIHBvcnRzOjwvc3Bhbj5cIiArXG4gICAgICAgICAgICAgICAgICAgIFwiPC90ZD48dGQ+XCI7XG4gICAgICAgICAgICAgICAgbG9jYWxUcmFuc3BvcnQgKz0gXCI8dHI+XCIgK1xuICAgICAgICAgICAgICAgICAgICBcIjx0ZD5cIiArXG4gICAgICAgICAgICAgICAgICAgIFwiPHNwYW4gY2xhc3M9J2ppdHNpcG9wb3Zlcl9ibHVlJz5Mb2NhbCBwb3J0czo8L3NwYW4+XCIgK1xuICAgICAgICAgICAgICAgICAgICBcIjwvdGQ+PHRkPlwiO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHRyYW5zcG9ydCArPVxuICAgICAgICAgICAgICAgICAgICBcIjx0cj5cIiArXG4gICAgICAgICAgICAgICAgICAgIFwiPHRkPlwiICtcbiAgICAgICAgICAgICAgICAgICAgXCI8c3BhbiBjbGFzcz0naml0c2lwb3BvdmVyX2JsdWUnPlJlbW90ZSBwb3J0Ojwvc3Bhbj5cIiArXG4gICAgICAgICAgICAgICAgICAgIFwiPC90ZD48dGQ+XCI7XG4gICAgICAgICAgICAgICAgbG9jYWxUcmFuc3BvcnQgKz1cbiAgICAgICAgICAgICAgICAgICAgXCI8dHI+XCIgK1xuICAgICAgICAgICAgICAgICAgICBcIjx0ZD5cIiArXG4gICAgICAgICAgICAgICAgICAgIFwiPHNwYW4gY2xhc3M9J2ppdHNpcG9wb3Zlcl9ibHVlJz5Mb2NhbCBwb3J0Ojwvc3Bhbj5cIiArXG4gICAgICAgICAgICAgICAgICAgIFwiPC90ZD48dGQ+XCI7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRyYW5zcG9ydCArPVxuICAgICAgICAgICAgICAgIENvbm5lY3Rpb25JbmRpY2F0b3IuZ2V0U3RyaW5nRnJvbUFycmF5KGRhdGEucmVtb3RlUG9ydCk7XG4gICAgICAgICAgICBsb2NhbFRyYW5zcG9ydCArPVxuICAgICAgICAgICAgICAgIENvbm5lY3Rpb25JbmRpY2F0b3IuZ2V0U3RyaW5nRnJvbUFycmF5KGRhdGEubG9jYWxQb3J0KTtcbiAgICAgICAgICAgIHRyYW5zcG9ydCArPSBcIjwvdGQ+PC90cj5cIjtcbiAgICAgICAgICAgIHRyYW5zcG9ydCArPSBsb2NhbFRyYW5zcG9ydCArIFwiPC90ZD48L3RyPlwiO1xuICAgICAgICAgICAgdHJhbnNwb3J0ICs9XCI8dHI+XCIgK1xuICAgICAgICAgICAgICAgIFwiPHRkPjxzcGFuIGNsYXNzPSdqaXRzaXBvcG92ZXJfYmx1ZSc+VHJhbnNwb3J0Ojwvc3Bhbj48L3RkPlwiICtcbiAgICAgICAgICAgICAgICBcIjx0ZD5cIiArIHRoaXMudHJhbnNwb3J0WzBdLnR5cGUgKyBcIjwvdGQ+PC90cj5cIjtcblxuICAgICAgICB9XG5cbiAgICAgICAgcmVzdWx0ICs9IFwiPHRhYmxlICBzdHlsZT0nd2lkdGg6MTAwJSc+XCIgK1xuICAgICAgICAgICAgXCI8dHI+XCIgK1xuICAgICAgICAgICAgXCI8dGQ+XCIgK1xuICAgICAgICAgICAgXCI8c3BhbiBjbGFzcz0naml0c2lwb3BvdmVyX2JsdWUnPkVzdGltYXRlZCBiYW5kd2lkdGg6PC9zcGFuPlwiICtcbiAgICAgICAgICAgIFwiPC90ZD48dGQ+XCIgK1xuICAgICAgICAgICAgXCI8c3BhbiBjbGFzcz0naml0c2lwb3BvdmVyX2dyZWVuJz4mZGFycjs8L3NwYW4+XCIgK1xuICAgICAgICAgICAgZG93bmxvYWRCYW5kd2lkdGggK1xuICAgICAgICAgICAgXCIgPHNwYW4gY2xhc3M9J2ppdHNpcG9wb3Zlcl9vcmFuZ2UnPiZ1YXJyOzwvc3Bhbj5cIiArXG4gICAgICAgICAgICB1cGxvYWRCYW5kd2lkdGggKyBcIjwvdGQ+PC90cj5cIjtcblxuICAgICAgICByZXN1bHQgKz0gdHJhbnNwb3J0ICsgXCI8L3RhYmxlPlwiO1xuXG4gICAgfVxuXG4gICAgcmV0dXJuIHJlc3VsdDtcbn07XG5cbi8qKlxuICogU2hvd3Mgb3IgaGlkZSB0aGUgYWRkaXRpb25hbCBpbmZvcm1hdGlvbi5cbiAqL1xuQ29ubmVjdGlvbkluZGljYXRvci5wcm90b3R5cGUuc2hvd01vcmUgPSBmdW5jdGlvbiAoKSB7XG4gICAgdGhpcy5zaG93TW9yZVZhbHVlID0gIXRoaXMuc2hvd01vcmVWYWx1ZTtcbiAgICB0aGlzLnVwZGF0ZVBvcG92ZXJEYXRhKCk7XG59O1xuXG5cbmZ1bmN0aW9uIGNyZWF0ZUljb24oY2xhc3NlcylcbntcbiAgICB2YXIgaWNvbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJzcGFuXCIpO1xuICAgIGZvcih2YXIgaSBpbiBjbGFzc2VzKVxuICAgIHtcbiAgICAgICAgaWNvbi5jbGFzc0xpc3QuYWRkKGNsYXNzZXNbaV0pO1xuICAgIH1cbiAgICBpY29uLmFwcGVuZENoaWxkKFxuICAgICAgICBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiaVwiKSkuY2xhc3NMaXN0LmFkZChcImljb24tY29ubmVjdGlvblwiKTtcbiAgICByZXR1cm4gaWNvbjtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIHRoZSBpbmRpY2F0b3JcbiAqL1xuQ29ubmVjdGlvbkluZGljYXRvci5wcm90b3R5cGUuY3JlYXRlID0gZnVuY3Rpb24gKCkge1xuICAgIHRoaXMuY29ubmVjdGlvbkluZGljYXRvckNvbnRhaW5lciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoXCJkaXZcIik7XG4gICAgdGhpcy5jb25uZWN0aW9uSW5kaWNhdG9yQ29udGFpbmVyLmNsYXNzTmFtZSA9IFwiY29ubmVjdGlvbmluZGljYXRvclwiO1xuICAgIHRoaXMuY29ubmVjdGlvbkluZGljYXRvckNvbnRhaW5lci5zdHlsZS5kaXNwbGF5ID0gXCJub25lXCI7XG4gICAgdGhpcy52aWRlb0NvbnRhaW5lci5hcHBlbmRDaGlsZCh0aGlzLmNvbm5lY3Rpb25JbmRpY2F0b3JDb250YWluZXIpO1xuICAgIHRoaXMucG9wb3ZlciA9IG5ldyBKaXRzaVBvcG92ZXIoXG4gICAgICAgICQoXCIjXCIgKyB0aGlzLnZpZGVvQ29udGFpbmVyLmlkICsgXCIgPiAuY29ubmVjdGlvbmluZGljYXRvclwiKSxcbiAgICAgICAge2NvbnRlbnQ6IFwiPGRpdiBjbGFzcz1cXFwiY29ubmVjdGlvbl9pbmZvXFxcIj5Db21lIGJhY2sgaGVyZSBmb3IgXCIgK1xuICAgICAgICAgICAgXCJjb25uZWN0aW9uIGluZm9ybWF0aW9uIG9uY2UgdGhlIGNvbmZlcmVuY2Ugc3RhcnRzPC9kaXY+XCIsXG4gICAgICAgICAgICBza2luOiBcImJsYWNrXCJ9KTtcblxuICAgIHRoaXMuZW1wdHlJY29uID0gdGhpcy5jb25uZWN0aW9uSW5kaWNhdG9yQ29udGFpbmVyLmFwcGVuZENoaWxkKFxuICAgICAgICBjcmVhdGVJY29uKFtcImNvbm5lY3Rpb25cIiwgXCJjb25uZWN0aW9uX2VtcHR5XCJdKSk7XG4gICAgdGhpcy5mdWxsSWNvbiA9IHRoaXMuY29ubmVjdGlvbkluZGljYXRvckNvbnRhaW5lci5hcHBlbmRDaGlsZChcbiAgICAgICAgY3JlYXRlSWNvbihbXCJjb25uZWN0aW9uXCIsIFwiY29ubmVjdGlvbl9mdWxsXCJdKSk7XG5cbn07XG5cbi8qKlxuICogUmVtb3ZlcyB0aGUgaW5kaWNhdG9yXG4gKi9cbkNvbm5lY3Rpb25JbmRpY2F0b3IucHJvdG90eXBlLnJlbW92ZSA9IGZ1bmN0aW9uKClcbntcbiAgICB0aGlzLmNvbm5lY3Rpb25JbmRpY2F0b3JDb250YWluZXIucmVtb3ZlKCk7XG4gICAgdGhpcy5wb3BvdmVyLmZvcmNlSGlkZSgpO1xuXG59O1xuXG4vKipcbiAqIFVwZGF0ZXMgdGhlIGRhdGEgb2YgdGhlIGluZGljYXRvclxuICogQHBhcmFtIHBlcmNlbnQgdGhlIHBlcmNlbnQgb2YgY29ubmVjdGlvbiBxdWFsaXR5XG4gKiBAcGFyYW0gb2JqZWN0IHRoZSBzdGF0aXN0aWNzIGRhdGEuXG4gKi9cbkNvbm5lY3Rpb25JbmRpY2F0b3IucHJvdG90eXBlLnVwZGF0ZUNvbm5lY3Rpb25RdWFsaXR5ID1cbmZ1bmN0aW9uIChwZXJjZW50LCBvYmplY3QpIHtcblxuICAgIGlmKHBlcmNlbnQgPT09IG51bGwpXG4gICAge1xuICAgICAgICB0aGlzLmNvbm5lY3Rpb25JbmRpY2F0b3JDb250YWluZXIuc3R5bGUuZGlzcGxheSA9IFwibm9uZVwiO1xuICAgICAgICB0aGlzLnBvcG92ZXIuZm9yY2VIaWRlKCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgZWxzZVxuICAgIHtcbiAgICAgICAgaWYodGhpcy5jb25uZWN0aW9uSW5kaWNhdG9yQ29udGFpbmVyLnN0eWxlLmRpc3BsYXkgPT0gXCJub25lXCIpIHtcbiAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbkluZGljYXRvckNvbnRhaW5lci5zdHlsZS5kaXNwbGF5ID0gXCJibG9ja1wiO1xuICAgICAgICAgICAgdGhpcy52aWRlb0xheW91dC51cGRhdGVNdXRlUG9zaXRpb24odGhpcy52aWRlb0NvbnRhaW5lci5pZCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5iYW5kd2lkdGggPSBvYmplY3QuYmFuZHdpZHRoO1xuICAgIHRoaXMuYml0cmF0ZSA9IG9iamVjdC5iaXRyYXRlO1xuICAgIHRoaXMucGFja2V0TG9zcyA9IG9iamVjdC5wYWNrZXRMb3NzO1xuICAgIHRoaXMudHJhbnNwb3J0ID0gb2JqZWN0LnRyYW5zcG9ydDtcbiAgICBpZihvYmplY3QucmVzb2x1dGlvbilcbiAgICB7XG4gICAgICAgIHRoaXMucmVzb2x1dGlvbiA9IG9iamVjdC5yZXNvbHV0aW9uO1xuICAgIH1cbiAgICBmb3IodmFyIHF1YWxpdHkgaW4gQ29ubmVjdGlvbkluZGljYXRvci5jb25uZWN0aW9uUXVhbGl0eVZhbHVlcylcbiAgICB7XG4gICAgICAgIGlmKHBlcmNlbnQgPj0gcXVhbGl0eSlcbiAgICAgICAge1xuICAgICAgICAgICAgdGhpcy5mdWxsSWNvbi5zdHlsZS53aWR0aCA9XG4gICAgICAgICAgICAgICAgQ29ubmVjdGlvbkluZGljYXRvci5jb25uZWN0aW9uUXVhbGl0eVZhbHVlc1txdWFsaXR5XTtcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aGlzLnVwZGF0ZVBvcG92ZXJEYXRhKCk7XG59O1xuXG4vKipcbiAqIFVwZGF0ZXMgdGhlIHJlc29sdXRpb25cbiAqIEBwYXJhbSByZXNvbHV0aW9uIHRoZSBuZXcgcmVzb2x1dGlvblxuICovXG5Db25uZWN0aW9uSW5kaWNhdG9yLnByb3RvdHlwZS51cGRhdGVSZXNvbHV0aW9uID0gZnVuY3Rpb24gKHJlc29sdXRpb24pIHtcbiAgICB0aGlzLnJlc29sdXRpb24gPSByZXNvbHV0aW9uO1xuICAgIHRoaXMudXBkYXRlUG9wb3ZlckRhdGEoKTtcbn07XG5cbi8qKlxuICogVXBkYXRlcyB0aGUgY29udGVudCBvZiB0aGUgcG9wb3ZlclxuICovXG5Db25uZWN0aW9uSW5kaWNhdG9yLnByb3RvdHlwZS51cGRhdGVQb3BvdmVyRGF0YSA9IGZ1bmN0aW9uICgpIHtcbiAgICB0aGlzLnBvcG92ZXIudXBkYXRlQ29udGVudChcbiAgICAgICAgICAgIFwiPGRpdiBjbGFzcz1cXFwiY29ubmVjdGlvbl9pbmZvXFxcIj5cIiArIHRoaXMuZ2VuZXJhdGVUZXh0KCkgKyBcIjwvZGl2PlwiKTtcbn07XG5cbi8qKlxuICogSGlkZXMgdGhlIHBvcG92ZXJcbiAqL1xuQ29ubmVjdGlvbkluZGljYXRvci5wcm90b3R5cGUuaGlkZSA9IGZ1bmN0aW9uICgpIHtcbiAgICB0aGlzLnBvcG92ZXIuZm9yY2VIaWRlKCk7XG59O1xuXG4vKipcbiAqIEhpZGVzIHRoZSBpbmRpY2F0b3JcbiAqL1xuQ29ubmVjdGlvbkluZGljYXRvci5wcm90b3R5cGUuaGlkZUluZGljYXRvciA9IGZ1bmN0aW9uICgpIHtcbiAgICB0aGlzLmNvbm5lY3Rpb25JbmRpY2F0b3JDb250YWluZXIuc3R5bGUuZGlzcGxheSA9IFwibm9uZVwiO1xuICAgIGlmKHRoaXMucG9wb3ZlcilcbiAgICAgICAgdGhpcy5wb3BvdmVyLmZvcmNlSGlkZSgpO1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBDb25uZWN0aW9uSW5kaWNhdG9yOyIsInZhciBBdWRpb0xldmVscyA9IHJlcXVpcmUoXCIuLi9hdWRpb19sZXZlbHMvQXVkaW9MZXZlbHNcIik7XG52YXIgQXZhdGFyID0gcmVxdWlyZShcIi4uL2F2YXRhci9BdmF0YXJcIik7XG52YXIgQ2hhdCA9IHJlcXVpcmUoXCIuLi9zaWRlX3Bhbm5lbHMvY2hhdC9DaGF0XCIpO1xudmFyIENvbnRhY3RMaXN0ID0gcmVxdWlyZShcIi4uL3NpZGVfcGFubmVscy9jb250YWN0bGlzdC9Db250YWN0TGlzdFwiKTtcbnZhciBVSVV0aWwgPSByZXF1aXJlKFwiLi4vdXRpbC9VSVV0aWxcIik7XG52YXIgQ29ubmVjdGlvbkluZGljYXRvciA9IHJlcXVpcmUoXCIuL0Nvbm5lY3Rpb25JbmRpY2F0b3JcIik7XG5cbnZhciBjdXJyZW50RG9taW5hbnRTcGVha2VyID0gbnVsbDtcbnZhciBsYXN0TkNvdW50ID0gY29uZmlnLmNoYW5uZWxMYXN0TjtcbnZhciBsb2NhbExhc3ROQ291bnQgPSBjb25maWcuY2hhbm5lbExhc3ROO1xudmFyIGxvY2FsTGFzdE5TZXQgPSBbXTtcbnZhciBsYXN0TkVuZHBvaW50c0NhY2hlID0gW107XG52YXIgbGFzdE5QaWNrdXBKaWQgPSBudWxsO1xudmFyIGxhcmdlVmlkZW9TdGF0ZSA9IHtcbiAgICB1cGRhdGVJblByb2dyZXNzOiBmYWxzZSxcbiAgICBuZXdTcmM6ICcnXG59O1xuXG4vKipcbiAqIEluZGljYXRlcyBpZiB3ZSBoYXZlIG11dGVkIG91ciBhdWRpbyBiZWZvcmUgdGhlIGNvbmZlcmVuY2UgaGFzIHN0YXJ0ZWQuXG4gKiBAdHlwZSB7Ym9vbGVhbn1cbiAqL1xudmFyIHByZU11dGVkID0gZmFsc2U7XG5cbnZhciBtdXRlZEF1ZGlvcyA9IHt9O1xuXG52YXIgZmxpcFhMb2NhbFZpZGVvID0gdHJ1ZTtcbnZhciBjdXJyZW50VmlkZW9XaWR0aCA9IG51bGw7XG52YXIgY3VycmVudFZpZGVvSGVpZ2h0ID0gbnVsbDtcblxudmFyIGxvY2FsVmlkZW9TcmMgPSBudWxsO1xuXG52YXIgZGVmYXVsdExvY2FsRGlzcGxheU5hbWUgPSBcIk1lXCI7XG5cbmZ1bmN0aW9uIHZpZGVvYWN0aXZlKCB2aWRlb2VsZW0pIHtcbiAgICBpZiAodmlkZW9lbGVtLmF0dHIoJ2lkJykuaW5kZXhPZignbWl4ZWRtc2xhYmVsJykgPT09IC0xKSB7XG4gICAgICAgIC8vIGlnbm9yZSBtaXhlZG1zbGFiZWxhMCBhbmQgdjBcblxuICAgICAgICB2aWRlb2VsZW0uc2hvdygpO1xuICAgICAgICBWaWRlb0xheW91dC5yZXNpemVUaHVtYm5haWxzKCk7XG5cbiAgICAgICAgdmFyIHZpZGVvUGFyZW50ID0gdmlkZW9lbGVtLnBhcmVudCgpO1xuICAgICAgICB2YXIgcGFyZW50UmVzb3VyY2VKaWQgPSBudWxsO1xuICAgICAgICBpZiAodmlkZW9QYXJlbnQpXG4gICAgICAgICAgICBwYXJlbnRSZXNvdXJjZUppZFxuICAgICAgICAgICAgICAgID0gVmlkZW9MYXlvdXQuZ2V0UGVlckNvbnRhaW5lclJlc291cmNlSmlkKHZpZGVvUGFyZW50WzBdKTtcblxuICAgICAgICAvLyBVcGRhdGUgdGhlIGxhcmdlIHZpZGVvIHRvIHRoZSBsYXN0IGFkZGVkIHZpZGVvIG9ubHkgaWYgdGhlcmUncyBub1xuICAgICAgICAvLyBjdXJyZW50IGRvbWluYW50LCBmb2N1c2VkIHNwZWFrZXIgb3IgcHJlemkgcGxheWluZyBvciB1cGRhdGUgaXQgdG9cbiAgICAgICAgLy8gdGhlIGN1cnJlbnQgZG9taW5hbnQgc3BlYWtlci5cbiAgICAgICAgaWYgKCghZm9jdXNlZFZpZGVvSW5mbyAmJlxuICAgICAgICAgICAgIVZpZGVvTGF5b3V0LmdldERvbWluYW50U3BlYWtlclJlc291cmNlSmlkKCkgJiZcbiAgICAgICAgICAgICFyZXF1aXJlKFwiLi4vcHJlemkvUHJlemlcIikuaXNQcmVzZW50YXRpb25WaXNpYmxlKCkpIHx8XG4gICAgICAgICAgICAocGFyZW50UmVzb3VyY2VKaWQgJiZcbiAgICAgICAgICAgICAgICBWaWRlb0xheW91dC5nZXREb21pbmFudFNwZWFrZXJSZXNvdXJjZUppZCgpID09PSBwYXJlbnRSZXNvdXJjZUppZCkpIHtcbiAgICAgICAgICAgIFZpZGVvTGF5b3V0LnVwZGF0ZUxhcmdlVmlkZW8oXG4gICAgICAgICAgICAgICAgUlRDLmdldFZpZGVvU3JjKHZpZGVvZWxlbVswXSksXG4gICAgICAgICAgICAgICAgMSxcbiAgICAgICAgICAgICAgICBwYXJlbnRSZXNvdXJjZUppZCk7XG4gICAgICAgIH1cblxuICAgICAgICBWaWRlb0xheW91dC5zaG93TW9kZXJhdG9ySW5kaWNhdG9yKCk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiB3YWl0Rm9yUmVtb3RlVmlkZW8oc2VsZWN0b3IsIHNzcmMsIHN0cmVhbSwgamlkKSB7XG4gICAgLy8gWFhYKGdwKSBzbywgZXZlcnkgY2FsbCB0byB0aGlzIGZ1bmN0aW9uIGlzICphbHdheXMqIHByZWNlZGVkIGJ5IGEgY2FsbFxuICAgIC8vIHRvIHRoZSBSVEMuYXR0YWNoTWVkaWFTdHJlYW0oKSBmdW5jdGlvbiBidXQgdGhhdCBjYWxsIGlzICpub3QqIGZvbGxvd2VkXG4gICAgLy8gYnkgYW4gdXBkYXRlIHRvIHRoZSB2aWRlb1NyY1RvU3NyYyBtYXAhXG4gICAgLy9cbiAgICAvLyBUaGUgYWJvdmUgd2F5IG9mIGRvaW5nIHRoaW5ncyByZXN1bHRzIGluIHZpZGVvIFNSQ3MgdGhhdCBkb24ndCBjb3JyZXNwb25kXG4gICAgLy8gdG8gYW55IFNTUkMgZm9yIGEgc2hvcnQgcGVyaW9kIG9mIHRpbWUgKHRvIGJlIG1vcmUgcHJlY2lzZSwgZm9yIGFzIGxvbmdcbiAgICAvLyB0aGUgd2FpdEZvclJlbW90ZVZpZGVvIHRha2VzIHRvIGNvbXBsZXRlKS4gVGhpcyBjYXVzZXMgcHJvYmxlbXMgKHNlZVxuICAgIC8vIGJlbGxvdykuXG4gICAgLy9cbiAgICAvLyBJJ20gd29uZGVyaW5nIHdoeSB3ZSBuZWVkIHRvIGRvIHRoYXQ7IGkuZS4gd2h5IGNhbGwgUlRDLmF0dGFjaE1lZGlhU3RyZWFtKClcbiAgICAvLyBhIHNlY29uZCB0aW1lIGluIGhlcmUgYW5kIG9ubHkgdGhlbiB1cGRhdGUgdGhlIHZpZGVvU3JjVG9Tc3JjIG1hcD8gV2h5XG4gICAgLy8gbm90IHNpbXBseSB1cGRhdGUgdGhlIHZpZGVvU3JjVG9Tc3JjIG1hcCB3aGVuIHRoZSBSVEMuYXR0YWNoTWVkaWFTdHJlYW0oKVxuICAgIC8vIGlzIGNhbGxlZCB0aGUgZmlyc3QgdGltZT8gSSBhY3R1YWxseSBkbyB0aGF0IGluIHRoZSBsYXN0TiBjaGFuZ2VkIGV2ZW50XG4gICAgLy8gaGFuZGxlciBiZWNhdXNlIHRoZSBcIm9ycGhhblwiIHZpZGVvIFNSQyBpcyBjYXVzaW5nIHRyb3VibGVzIHRoZXJlLiBUaGVcbiAgICAvLyBwdXJwb3NlIG9mIHRoaXMgbWV0aG9kIHdvdWxkIHRoZW4gYmUgdG8gZmlyZSB0aGUgXCJ2aWRlb2FjdGl2ZS5qaW5nbGVcIi5cbiAgICAvL1xuICAgIC8vIEZvb2QgZm9yIHRob3VnaCBJIGd1ZXNzIDotKVxuXG4gICAgaWYgKHNlbGVjdG9yLnJlbW92ZWQgfHwgIXNlbGVjdG9yLnBhcmVudCgpLmlzKFwiOnZpc2libGVcIikpIHtcbiAgICAgICAgY29uc29sZS53YXJuKFwiTWVkaWEgcmVtb3ZlZCBiZWZvcmUgaGFkIHN0YXJ0ZWRcIiwgc2VsZWN0b3IpO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKHN0cmVhbS5pZCA9PT0gJ21peGVkbXNsYWJlbCcpIHJldHVybjtcblxuICAgIGlmIChzZWxlY3RvclswXS5jdXJyZW50VGltZSA+IDApIHtcbiAgICAgICAgdmFyIHZpZGVvU3RyZWFtID0gc2ltdWxjYXN0LmdldFJlY2VpdmluZ1ZpZGVvU3RyZWFtKHN0cmVhbSk7XG4gICAgICAgIFJUQy5hdHRhY2hNZWRpYVN0cmVhbShzZWxlY3RvciwgdmlkZW9TdHJlYW0pOyAvLyBGSVhNRTogd2h5IGRvIGkgaGF2ZSB0byBkbyB0aGlzIGZvciBGRj9cblxuICAgICAgICAvLyBGSVhNRTogYWRkIGEgY2xhc3MgdGhhdCB3aWxsIGFzc29jaWF0ZSBwZWVyIEppZCwgdmlkZW8uc3JjLCBpdCdzIHNzcmMgYW5kIHZpZGVvIHR5cGVcbiAgICAgICAgLy8gICAgICAgIGluIG9yZGVyIHRvIGdldCByaWQgb2YgdG9vIG1hbnkgbWFwc1xuICAgICAgICBpZiAoc3NyYyAmJiBqaWQpIHtcbiAgICAgICAgICAgIGppZDJTc3JjW1N0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCldID0gc3NyYztcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGNvbnNvbGUud2FybihcIk5vIHNzcmMgZ2l2ZW4gZm9yIGppZFwiLCBqaWQpO1xuICAgICAgICB9XG5cbiAgICAgICAgdmlkZW9hY3RpdmUoc2VsZWN0b3IpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgd2FpdEZvclJlbW90ZVZpZGVvKHNlbGVjdG9yLCBzc3JjLCBzdHJlYW0sIGppZCk7XG4gICAgICAgIH0sIDI1MCk7XG4gICAgfVxufVxuXG4vKipcbiAqIFJldHVybnMgYW4gYXJyYXkgb2YgdGhlIHZpZGVvIGhvcml6b250YWwgYW5kIHZlcnRpY2FsIGluZGVudHMsXG4gKiBzbyB0aGF0IGlmIGZpdHMgaXRzIHBhcmVudC5cbiAqXG4gKiBAcmV0dXJuIGFuIGFycmF5IHdpdGggMiBlbGVtZW50cywgdGhlIGhvcml6b250YWwgaW5kZW50IGFuZCB0aGUgdmVydGljYWxcbiAqIGluZGVudFxuICovXG5mdW5jdGlvbiBnZXRDYW1lcmFWaWRlb1Bvc2l0aW9uKHZpZGVvV2lkdGgsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvSGVpZ2h0LFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2aWRlb1NwYWNlV2lkdGgsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvU3BhY2VIZWlnaHQpIHtcbiAgICAvLyBQYXJlbnQgaGVpZ2h0IGlzbid0IGNvbXBsZXRlbHkgY2FsY3VsYXRlZCB3aGVuIHdlIHBvc2l0aW9uIHRoZSB2aWRlbyBpblxuICAgIC8vIGZ1bGwgc2NyZWVuIG1vZGUgYW5kIHRoaXMgaXMgd2h5IHdlIHVzZSB0aGUgc2NyZWVuIGhlaWdodCBpbiB0aGlzIGNhc2UuXG4gICAgLy8gTmVlZCB0byB0aGluayBpdCBmdXJ0aGVyIGF0IHNvbWUgcG9pbnQgYW5kIGltcGxlbWVudCBpdCBwcm9wZXJseS5cbiAgICB2YXIgaXNGdWxsU2NyZWVuID0gZG9jdW1lbnQuZnVsbFNjcmVlbiB8fFxuICAgICAgICBkb2N1bWVudC5tb3pGdWxsU2NyZWVuIHx8XG4gICAgICAgIGRvY3VtZW50LndlYmtpdElzRnVsbFNjcmVlbjtcbiAgICBpZiAoaXNGdWxsU2NyZWVuKVxuICAgICAgICB2aWRlb1NwYWNlSGVpZ2h0ID0gd2luZG93LmlubmVySGVpZ2h0O1xuXG4gICAgdmFyIGhvcml6b250YWxJbmRlbnQgPSAodmlkZW9TcGFjZVdpZHRoIC0gdmlkZW9XaWR0aCkgLyAyO1xuICAgIHZhciB2ZXJ0aWNhbEluZGVudCA9ICh2aWRlb1NwYWNlSGVpZ2h0IC0gdmlkZW9IZWlnaHQpIC8gMjtcblxuICAgIHJldHVybiBbaG9yaXpvbnRhbEluZGVudCwgdmVydGljYWxJbmRlbnRdO1xufVxuXG4vKipcbiAqIFJldHVybnMgYW4gYXJyYXkgb2YgdGhlIHZpZGVvIGhvcml6b250YWwgYW5kIHZlcnRpY2FsIGluZGVudHMuXG4gKiBDZW50ZXJzIGhvcml6b250YWxseSBhbmQgdG9wIGFsaWducyB2ZXJ0aWNhbGx5LlxuICpcbiAqIEByZXR1cm4gYW4gYXJyYXkgd2l0aCAyIGVsZW1lbnRzLCB0aGUgaG9yaXpvbnRhbCBpbmRlbnQgYW5kIHRoZSB2ZXJ0aWNhbFxuICogaW5kZW50XG4gKi9cbmZ1bmN0aW9uIGdldERlc2t0b3BWaWRlb1Bvc2l0aW9uKHZpZGVvV2lkdGgsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2aWRlb0hlaWdodCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvU3BhY2VXaWR0aCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvU3BhY2VIZWlnaHQpIHtcblxuICAgIHZhciBob3Jpem9udGFsSW5kZW50ID0gKHZpZGVvU3BhY2VXaWR0aCAtIHZpZGVvV2lkdGgpIC8gMjtcblxuICAgIHZhciB2ZXJ0aWNhbEluZGVudCA9IDA7Ly8gVG9wIGFsaWduZWRcblxuICAgIHJldHVybiBbaG9yaXpvbnRhbEluZGVudCwgdmVydGljYWxJbmRlbnRdO1xufVxuXG5cbi8qKlxuICogUmV0dXJucyBhbiBhcnJheSBvZiB0aGUgdmlkZW8gZGltZW5zaW9ucywgc28gdGhhdCBpdCBjb3ZlcnMgdGhlIHNjcmVlbi5cbiAqIEl0IGxlYXZlcyBubyBlbXB0eSBhcmVhcywgYnV0IHNvbWUgcGFydHMgb2YgdGhlIHZpZGVvIG1pZ2h0IG5vdCBiZSB2aXNpYmxlLlxuICpcbiAqIEByZXR1cm4gYW4gYXJyYXkgd2l0aCAyIGVsZW1lbnRzLCB0aGUgdmlkZW8gd2lkdGggYW5kIHRoZSB2aWRlbyBoZWlnaHRcbiAqL1xuZnVuY3Rpb24gZ2V0Q2FtZXJhVmlkZW9TaXplKHZpZGVvV2lkdGgsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmlkZW9IZWlnaHQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmlkZW9TcGFjZVdpZHRoLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvU3BhY2VIZWlnaHQpIHtcbiAgICBpZiAoIXZpZGVvV2lkdGgpXG4gICAgICAgIHZpZGVvV2lkdGggPSBjdXJyZW50VmlkZW9XaWR0aDtcbiAgICBpZiAoIXZpZGVvSGVpZ2h0KVxuICAgICAgICB2aWRlb0hlaWdodCA9IGN1cnJlbnRWaWRlb0hlaWdodDtcblxuICAgIHZhciBhc3BlY3RSYXRpbyA9IHZpZGVvV2lkdGggLyB2aWRlb0hlaWdodDtcblxuICAgIHZhciBhdmFpbGFibGVXaWR0aCA9IE1hdGgubWF4KHZpZGVvV2lkdGgsIHZpZGVvU3BhY2VXaWR0aCk7XG4gICAgdmFyIGF2YWlsYWJsZUhlaWdodCA9IE1hdGgubWF4KHZpZGVvSGVpZ2h0LCB2aWRlb1NwYWNlSGVpZ2h0KTtcblxuICAgIGlmIChhdmFpbGFibGVXaWR0aCAvIGFzcGVjdFJhdGlvIDwgdmlkZW9TcGFjZUhlaWdodCkge1xuICAgICAgICBhdmFpbGFibGVIZWlnaHQgPSB2aWRlb1NwYWNlSGVpZ2h0O1xuICAgICAgICBhdmFpbGFibGVXaWR0aCA9IGF2YWlsYWJsZUhlaWdodCAqIGFzcGVjdFJhdGlvO1xuICAgIH1cblxuICAgIGlmIChhdmFpbGFibGVIZWlnaHQgKiBhc3BlY3RSYXRpbyA8IHZpZGVvU3BhY2VXaWR0aCkge1xuICAgICAgICBhdmFpbGFibGVXaWR0aCA9IHZpZGVvU3BhY2VXaWR0aDtcbiAgICAgICAgYXZhaWxhYmxlSGVpZ2h0ID0gYXZhaWxhYmxlV2lkdGggLyBhc3BlY3RSYXRpbztcbiAgICB9XG5cbiAgICByZXR1cm4gW2F2YWlsYWJsZVdpZHRoLCBhdmFpbGFibGVIZWlnaHRdO1xufVxuXG4vKipcbiAqIFNldHMgdGhlIGRpc3BsYXkgbmFtZSBmb3IgdGhlIGdpdmVuIHZpZGVvIHNwYW4gaWQuXG4gKi9cbmZ1bmN0aW9uIHNldERpc3BsYXlOYW1lKHZpZGVvU3BhbklkLCBkaXNwbGF5TmFtZSkge1xuICAgIHZhciBuYW1lU3BhbiA9ICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnPnNwYW4uZGlzcGxheW5hbWUnKTtcbiAgICB2YXIgZGVmYXVsdExvY2FsRGlzcGxheU5hbWUgPSBpbnRlcmZhY2VDb25maWcuREVGQVVMVF9MT0NBTF9ESVNQTEFZX05BTUU7XG5cbiAgICAvLyBJZiB3ZSBhbHJlYWR5IGhhdmUgYSBkaXNwbGF5IG5hbWUgZm9yIHRoaXMgdmlkZW8uXG4gICAgaWYgKG5hbWVTcGFuLmxlbmd0aCA+IDApIHtcbiAgICAgICAgdmFyIG5hbWVTcGFuRWxlbWVudCA9IG5hbWVTcGFuLmdldCgwKTtcblxuICAgICAgICBpZiAobmFtZVNwYW5FbGVtZW50LmlkID09PSAnbG9jYWxEaXNwbGF5TmFtZScgJiZcbiAgICAgICAgICAgICQoJyNsb2NhbERpc3BsYXlOYW1lJykudGV4dCgpICE9PSBkaXNwbGF5TmFtZSkge1xuICAgICAgICAgICAgaWYgKGRpc3BsYXlOYW1lICYmIGRpc3BsYXlOYW1lLmxlbmd0aCA+IDApXG4gICAgICAgICAgICAgICAgJCgnI2xvY2FsRGlzcGxheU5hbWUnKS5odG1sKGRpc3BsYXlOYW1lICsgJyAobWUpJyk7XG4gICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgJCgnI2xvY2FsRGlzcGxheU5hbWUnKS50ZXh0KGRlZmF1bHRMb2NhbERpc3BsYXlOYW1lKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGlmIChkaXNwbGF5TmFtZSAmJiBkaXNwbGF5TmFtZS5sZW5ndGggPiAwKVxuICAgICAgICAgICAgICAgICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnX25hbWUnKS5odG1sKGRpc3BsYXlOYW1lKTtcbiAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAkKCcjJyArIHZpZGVvU3BhbklkICsgJ19uYW1lJykudGV4dChpbnRlcmZhY2VDb25maWcuREVGQVVMVF9SRU1PVEVfRElTUExBWV9OQU1FKTtcbiAgICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAgIHZhciBlZGl0QnV0dG9uID0gbnVsbDtcblxuICAgICAgICBuYW1lU3BhbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NwYW4nKTtcbiAgICAgICAgbmFtZVNwYW4uY2xhc3NOYW1lID0gJ2Rpc3BsYXluYW1lJztcbiAgICAgICAgJCgnIycgKyB2aWRlb1NwYW5JZClbMF0uYXBwZW5kQ2hpbGQobmFtZVNwYW4pO1xuXG4gICAgICAgIGlmICh2aWRlb1NwYW5JZCA9PT0gJ2xvY2FsVmlkZW9Db250YWluZXInKSB7XG4gICAgICAgICAgICBlZGl0QnV0dG9uID0gY3JlYXRlRWRpdERpc3BsYXlOYW1lQnV0dG9uKCk7XG4gICAgICAgICAgICBuYW1lU3Bhbi5pbm5lclRleHQgPSBkZWZhdWx0TG9jYWxEaXNwbGF5TmFtZTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIG5hbWVTcGFuLmlubmVyVGV4dCA9IGludGVyZmFjZUNvbmZpZy5ERUZBVUxUX1JFTU9URV9ESVNQTEFZX05BTUU7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoZGlzcGxheU5hbWUgJiYgZGlzcGxheU5hbWUubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgbmFtZVNwYW4uaW5uZXJUZXh0ID0gZGlzcGxheU5hbWU7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWVkaXRCdXR0b24pIHtcbiAgICAgICAgICAgIG5hbWVTcGFuLmlkID0gdmlkZW9TcGFuSWQgKyAnX25hbWUnO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgbmFtZVNwYW4uaWQgPSAnbG9jYWxEaXNwbGF5TmFtZSc7XG4gICAgICAgICAgICAkKCcjJyArIHZpZGVvU3BhbklkKVswXS5hcHBlbmRDaGlsZChlZGl0QnV0dG9uKTtcblxuICAgICAgICAgICAgdmFyIGVkaXRhYmxlVGV4dCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2lucHV0Jyk7XG4gICAgICAgICAgICBlZGl0YWJsZVRleHQuY2xhc3NOYW1lID0gJ2Rpc3BsYXluYW1lJztcbiAgICAgICAgICAgIGVkaXRhYmxlVGV4dC50eXBlID0gJ3RleHQnO1xuICAgICAgICAgICAgZWRpdGFibGVUZXh0LmlkID0gJ2VkaXREaXNwbGF5TmFtZSc7XG5cbiAgICAgICAgICAgIGlmIChkaXNwbGF5TmFtZSAmJiBkaXNwbGF5TmFtZS5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICBlZGl0YWJsZVRleHQudmFsdWVcbiAgICAgICAgICAgICAgICAgICAgPSBkaXNwbGF5TmFtZS5zdWJzdHJpbmcoMCwgZGlzcGxheU5hbWUuaW5kZXhPZignIChtZSknKSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGVkaXRhYmxlVGV4dC5zZXRBdHRyaWJ1dGUoJ3N0eWxlJywgJ2Rpc3BsYXk6bm9uZTsnKTtcbiAgICAgICAgICAgIGVkaXRhYmxlVGV4dC5zZXRBdHRyaWJ1dGUoJ3BsYWNlaG9sZGVyJywgJ2V4LiBKYW5lIFBpbmsnKTtcbiAgICAgICAgICAgICQoJyMnICsgdmlkZW9TcGFuSWQpWzBdLmFwcGVuZENoaWxkKGVkaXRhYmxlVGV4dCk7XG5cbiAgICAgICAgICAgICQoJyNsb2NhbFZpZGVvQ29udGFpbmVyIC5kaXNwbGF5bmFtZScpXG4gICAgICAgICAgICAgICAgLmJpbmQoXCJjbGlja1wiLCBmdW5jdGlvbiAoZSkge1xuXG4gICAgICAgICAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgICAgICAgICAgZS5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICAgICAgICAgICAgICAgICAgJCgnI2xvY2FsRGlzcGxheU5hbWUnKS5oaWRlKCk7XG4gICAgICAgICAgICAgICAgICAgICQoJyNlZGl0RGlzcGxheU5hbWUnKS5zaG93KCk7XG4gICAgICAgICAgICAgICAgICAgICQoJyNlZGl0RGlzcGxheU5hbWUnKS5mb2N1cygpO1xuICAgICAgICAgICAgICAgICAgICAkKCcjZWRpdERpc3BsYXlOYW1lJykuc2VsZWN0KCk7XG5cbiAgICAgICAgICAgICAgICAgICAgJCgnI2VkaXREaXNwbGF5TmFtZScpLm9uZShcImZvY3Vzb3V0XCIsIGZ1bmN0aW9uIChlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBWaWRlb0xheW91dC5pbnB1dERpc3BsYXlOYW1lSGFuZGxlcih0aGlzLnZhbHVlKTtcbiAgICAgICAgICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgICAgICAgICAgJCgnI2VkaXREaXNwbGF5TmFtZScpLm9uKCdrZXlkb3duJywgZnVuY3Rpb24gKGUpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChlLmtleUNvZGUgPT09IDEzKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LmlucHV0RGlzcGxheU5hbWVIYW5kbGVyKHRoaXMudmFsdWUpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuLyoqXG4gKiBHZXRzIHRoZSBzZWxlY3RvciBvZiB2aWRlbyB0aHVtYm5haWwgY29udGFpbmVyIGZvciB0aGUgdXNlciBpZGVudGlmaWVkIGJ5XG4gKiBnaXZlbiA8dHQ+dXNlckppZDwvdHQ+XG4gKiBAcGFyYW0gcmVzb3VyY2VKaWQgdXNlcidzIEppZCBmb3Igd2hvbSB3ZSB3YW50IHRvIGdldCB0aGUgdmlkZW8gY29udGFpbmVyLlxuICovXG5mdW5jdGlvbiBnZXRQYXJ0aWNpcGFudENvbnRhaW5lcihyZXNvdXJjZUppZClcbntcbiAgICBpZiAoIXJlc291cmNlSmlkKVxuICAgICAgICByZXR1cm4gbnVsbDtcblxuICAgIGlmIChyZXNvdXJjZUppZCA9PT0geG1wcC5teVJlc291cmNlKCkpXG4gICAgICAgIHJldHVybiAkKFwiI2xvY2FsVmlkZW9Db250YWluZXJcIik7XG4gICAgZWxzZVxuICAgICAgICByZXR1cm4gJChcIiNwYXJ0aWNpcGFudF9cIiArIHJlc291cmNlSmlkKTtcbn1cblxuLyoqXG4gKiBTZXRzIHRoZSBzaXplIGFuZCBwb3NpdGlvbiBvZiB0aGUgZ2l2ZW4gdmlkZW8gZWxlbWVudC5cbiAqXG4gKiBAcGFyYW0gdmlkZW8gdGhlIHZpZGVvIGVsZW1lbnQgdG8gcG9zaXRpb25cbiAqIEBwYXJhbSB3aWR0aCB0aGUgZGVzaXJlZCB2aWRlbyB3aWR0aFxuICogQHBhcmFtIGhlaWdodCB0aGUgZGVzaXJlZCB2aWRlbyBoZWlnaHRcbiAqIEBwYXJhbSBob3Jpem9udGFsSW5kZW50IHRoZSBsZWZ0IGFuZCByaWdodCBpbmRlbnRcbiAqIEBwYXJhbSB2ZXJ0aWNhbEluZGVudCB0aGUgdG9wIGFuZCBib3R0b20gaW5kZW50XG4gKi9cbmZ1bmN0aW9uIHBvc2l0aW9uVmlkZW8odmlkZW8sXG4gICAgICAgICAgICAgICAgICAgICAgIHdpZHRoLFxuICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQsXG4gICAgICAgICAgICAgICAgICAgICAgIGhvcml6b250YWxJbmRlbnQsXG4gICAgICAgICAgICAgICAgICAgICAgIHZlcnRpY2FsSW5kZW50KSB7XG4gICAgdmlkZW8ud2lkdGgod2lkdGgpO1xuICAgIHZpZGVvLmhlaWdodChoZWlnaHQpO1xuICAgIHZpZGVvLmNzcyh7ICB0b3A6IHZlcnRpY2FsSW5kZW50ICsgJ3B4JyxcbiAgICAgICAgYm90dG9tOiB2ZXJ0aWNhbEluZGVudCArICdweCcsXG4gICAgICAgIGxlZnQ6IGhvcml6b250YWxJbmRlbnQgKyAncHgnLFxuICAgICAgICByaWdodDogaG9yaXpvbnRhbEluZGVudCArICdweCd9KTtcbn1cblxuLyoqXG4gKiBBZGRzIHRoZSByZW1vdGUgdmlkZW8gbWVudSBlbGVtZW50IGZvciB0aGUgZ2l2ZW4gPHR0PmppZDwvdHQ+IGluIHRoZVxuICogZ2l2ZW4gPHR0PnBhcmVudEVsZW1lbnQ8L3R0Pi5cbiAqXG4gKiBAcGFyYW0gamlkIHRoZSBqaWQgaW5kaWNhdGluZyB0aGUgdmlkZW8gZm9yIHdoaWNoIHdlJ3JlIGFkZGluZyBhIG1lbnUuXG4gKiBAcGFyYW0gcGFyZW50RWxlbWVudCB0aGUgcGFyZW50IGVsZW1lbnQgd2hlcmUgdGhpcyBtZW51IHdpbGwgYmUgYWRkZWRcbiAqL1xuZnVuY3Rpb24gYWRkUmVtb3RlVmlkZW9NZW51KGppZCwgcGFyZW50RWxlbWVudCkge1xuICAgIHZhciBzcGFuRWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NwYW4nKTtcbiAgICBzcGFuRWxlbWVudC5jbGFzc05hbWUgPSAncmVtb3RldmlkZW9tZW51JztcblxuICAgIHBhcmVudEVsZW1lbnQuYXBwZW5kQ2hpbGQoc3BhbkVsZW1lbnQpO1xuXG4gICAgdmFyIG1lbnVFbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnaScpO1xuICAgIG1lbnVFbGVtZW50LmNsYXNzTmFtZSA9ICdmYSBmYS1hbmdsZS1kb3duJztcbiAgICBtZW51RWxlbWVudC50aXRsZSA9ICdSZW1vdGUgdXNlciBjb250cm9scyc7XG4gICAgc3BhbkVsZW1lbnQuYXBwZW5kQ2hpbGQobWVudUVsZW1lbnQpO1xuXG4vLyAgICAgICAgPHVsIGNsYXNzPVwicG9wdXBtZW51XCI+XG4vLyAgICAgICAgPGxpPjxhIGhyZWY9XCIjXCI+TXV0ZTwvYT48L2xpPlxuLy8gICAgICAgIDxsaT48YSBocmVmPVwiI1wiPkVqZWN0PC9hPjwvbGk+XG4vLyAgICAgICAgPC91bD5cblxuICAgIHZhciBwb3B1cG1lbnVFbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgndWwnKTtcbiAgICBwb3B1cG1lbnVFbGVtZW50LmNsYXNzTmFtZSA9ICdwb3B1cG1lbnUnO1xuICAgIHBvcHVwbWVudUVsZW1lbnQuaWRcbiAgICAgICAgPSAncmVtb3RlX3BvcHVwbWVudV8nICsgU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKTtcbiAgICBzcGFuRWxlbWVudC5hcHBlbmRDaGlsZChwb3B1cG1lbnVFbGVtZW50KTtcblxuICAgIHZhciBtdXRlTWVudUl0ZW0gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdsaScpO1xuICAgIHZhciBtdXRlTGlua0l0ZW0gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJyk7XG5cbiAgICB2YXIgbXV0ZWRJbmRpY2F0b3IgPSBcIjxpIGNsYXNzPSdpY29uLW1pYy1kaXNhYmxlZCc+PC9pPlwiO1xuXG4gICAgaWYgKCFtdXRlZEF1ZGlvc1tqaWRdKSB7XG4gICAgICAgIG11dGVMaW5rSXRlbS5pbm5lckhUTUwgPSBtdXRlZEluZGljYXRvciArICdNdXRlJztcbiAgICAgICAgbXV0ZUxpbmtJdGVtLmNsYXNzTmFtZSA9ICdtdXRlbGluayc7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBtdXRlTGlua0l0ZW0uaW5uZXJIVE1MID0gbXV0ZWRJbmRpY2F0b3IgKyAnIE11dGVkJztcbiAgICAgICAgbXV0ZUxpbmtJdGVtLmNsYXNzTmFtZSA9ICdtdXRlbGluayBkaXNhYmxlZCc7XG4gICAgfVxuXG4gICAgbXV0ZUxpbmtJdGVtLm9uY2xpY2sgPSBmdW5jdGlvbigpe1xuICAgICAgICBpZiAoJCh0aGlzKS5hdHRyKCdkaXNhYmxlZCcpICE9IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgfVxuICAgICAgICB2YXIgaXNNdXRlID0gbXV0ZWRBdWRpb3NbamlkXSA9PSB0cnVlO1xuICAgICAgICB4bXBwLnNldE11dGUoamlkLCAhaXNNdXRlKTtcblxuICAgICAgICBwb3B1cG1lbnVFbGVtZW50LnNldEF0dHJpYnV0ZSgnc3R5bGUnLCAnZGlzcGxheTpub25lOycpO1xuXG4gICAgICAgIGlmIChpc011dGUpIHtcbiAgICAgICAgICAgIHRoaXMuaW5uZXJIVE1MID0gbXV0ZWRJbmRpY2F0b3IgKyAnIE11dGVkJztcbiAgICAgICAgICAgIHRoaXMuY2xhc3NOYW1lID0gJ211dGVsaW5rIGRpc2FibGVkJztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuaW5uZXJIVE1MID0gbXV0ZWRJbmRpY2F0b3IgKyAnIE11dGUnO1xuICAgICAgICAgICAgdGhpcy5jbGFzc05hbWUgPSAnbXV0ZWxpbmsnO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIG11dGVNZW51SXRlbS5hcHBlbmRDaGlsZChtdXRlTGlua0l0ZW0pO1xuICAgIHBvcHVwbWVudUVsZW1lbnQuYXBwZW5kQ2hpbGQobXV0ZU1lbnVJdGVtKTtcblxuICAgIHZhciBlamVjdEluZGljYXRvciA9IFwiPGkgY2xhc3M9J2ZhIGZhLWVqZWN0Jz48L2k+XCI7XG5cbiAgICB2YXIgZWplY3RNZW51SXRlbSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2xpJyk7XG4gICAgdmFyIGVqZWN0TGlua0l0ZW0gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJyk7XG4gICAgZWplY3RMaW5rSXRlbS5pbm5lckhUTUwgPSBlamVjdEluZGljYXRvciArICcgS2ljayBvdXQnO1xuICAgIGVqZWN0TGlua0l0ZW0ub25jbGljayA9IGZ1bmN0aW9uKCl7XG4gICAgICAgIHhtcHAuZWplY3QoamlkKTtcbiAgICAgICAgcG9wdXBtZW51RWxlbWVudC5zZXRBdHRyaWJ1dGUoJ3N0eWxlJywgJ2Rpc3BsYXk6bm9uZTsnKTtcbiAgICB9O1xuXG4gICAgZWplY3RNZW51SXRlbS5hcHBlbmRDaGlsZChlamVjdExpbmtJdGVtKTtcbiAgICBwb3B1cG1lbnVFbGVtZW50LmFwcGVuZENoaWxkKGVqZWN0TWVudUl0ZW0pO1xuXG4gICAgdmFyIHBhZGRpbmdTcGFuID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3BhbicpO1xuICAgIHBhZGRpbmdTcGFuLmNsYXNzTmFtZSA9ICdwb3B1cG1lbnVQYWRkaW5nJztcbiAgICBwb3B1cG1lbnVFbGVtZW50LmFwcGVuZENoaWxkKHBhZGRpbmdTcGFuKTtcbn1cblxuLyoqXG4gKiBSZW1vdmVzIHJlbW90ZSB2aWRlbyBtZW51IGVsZW1lbnQgZnJvbSB2aWRlbyBlbGVtZW50IGlkZW50aWZpZWQgYnlcbiAqIGdpdmVuIDx0dD52aWRlb0VsZW1lbnRJZDwvdHQ+LlxuICpcbiAqIEBwYXJhbSB2aWRlb0VsZW1lbnRJZCB0aGUgaWQgb2YgbG9jYWwgb3IgcmVtb3RlIHZpZGVvIGVsZW1lbnQuXG4gKi9cbmZ1bmN0aW9uIHJlbW92ZVJlbW90ZVZpZGVvTWVudSh2aWRlb0VsZW1lbnRJZCkge1xuICAgIHZhciBtZW51U3BhbiA9ICQoJyMnICsgdmlkZW9FbGVtZW50SWQgKyAnPnNwYW4ucmVtb3RldmlkZW9tZW51Jyk7XG4gICAgaWYgKG1lbnVTcGFuLmxlbmd0aCkge1xuICAgICAgICBtZW51U3Bhbi5yZW1vdmUoKTtcbiAgICB9XG59XG5cbi8qKlxuICogVXBkYXRlcyB0aGUgZGF0YSBmb3IgdGhlIGluZGljYXRvclxuICogQHBhcmFtIGlkIHRoZSBpZCBvZiB0aGUgaW5kaWNhdG9yXG4gKiBAcGFyYW0gcGVyY2VudCB0aGUgcGVyY2VudCBmb3IgY29ubmVjdGlvbiBxdWFsaXR5XG4gKiBAcGFyYW0gb2JqZWN0IHRoZSBkYXRhXG4gKi9cbmZ1bmN0aW9uIHVwZGF0ZVN0YXRzSW5kaWNhdG9yKGlkLCBwZXJjZW50LCBvYmplY3QpIHtcbiAgICBpZihWaWRlb0xheW91dC5jb25uZWN0aW9uSW5kaWNhdG9yc1tpZF0pXG4gICAgICAgIFZpZGVvTGF5b3V0LmNvbm5lY3Rpb25JbmRpY2F0b3JzW2lkXS51cGRhdGVDb25uZWN0aW9uUXVhbGl0eShwZXJjZW50LCBvYmplY3QpO1xufVxuXG5cbi8qKlxuICogUmV0dXJucyBhbiBhcnJheSBvZiB0aGUgdmlkZW8gZGltZW5zaW9ucywgc28gdGhhdCBpdCBrZWVwcyBpdCdzIGFzcGVjdFxuICogcmF0aW8gYW5kIGZpdHMgYXZhaWxhYmxlIGFyZWEgd2l0aCBpdCdzIGxhcmdlciBkaW1lbnNpb24uIFRoaXMgbWV0aG9kXG4gKiBlbnN1cmVzIHRoYXQgd2hvbGUgdmlkZW8gd2lsbCBiZSB2aXNpYmxlIGFuZCBjYW4gbGVhdmUgZW1wdHkgYXJlYXMuXG4gKlxuICogQHJldHVybiBhbiBhcnJheSB3aXRoIDIgZWxlbWVudHMsIHRoZSB2aWRlbyB3aWR0aCBhbmQgdGhlIHZpZGVvIGhlaWdodFxuICovXG5mdW5jdGlvbiBnZXREZXNrdG9wVmlkZW9TaXplKHZpZGVvV2lkdGgsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvSGVpZ2h0LFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2aWRlb1NwYWNlV2lkdGgsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvU3BhY2VIZWlnaHQpIHtcbiAgICBpZiAoIXZpZGVvV2lkdGgpXG4gICAgICAgIHZpZGVvV2lkdGggPSBjdXJyZW50VmlkZW9XaWR0aDtcbiAgICBpZiAoIXZpZGVvSGVpZ2h0KVxuICAgICAgICB2aWRlb0hlaWdodCA9IGN1cnJlbnRWaWRlb0hlaWdodDtcblxuICAgIHZhciBhc3BlY3RSYXRpbyA9IHZpZGVvV2lkdGggLyB2aWRlb0hlaWdodDtcblxuICAgIHZhciBhdmFpbGFibGVXaWR0aCA9IE1hdGgubWF4KHZpZGVvV2lkdGgsIHZpZGVvU3BhY2VXaWR0aCk7XG4gICAgdmFyIGF2YWlsYWJsZUhlaWdodCA9IE1hdGgubWF4KHZpZGVvSGVpZ2h0LCB2aWRlb1NwYWNlSGVpZ2h0KTtcblxuICAgIHZpZGVvU3BhY2VIZWlnaHQgLT0gJCgnI3JlbW90ZVZpZGVvcycpLm91dGVySGVpZ2h0KCk7XG5cbiAgICBpZiAoYXZhaWxhYmxlV2lkdGggLyBhc3BlY3RSYXRpbyA+PSB2aWRlb1NwYWNlSGVpZ2h0KVxuICAgIHtcbiAgICAgICAgYXZhaWxhYmxlSGVpZ2h0ID0gdmlkZW9TcGFjZUhlaWdodDtcbiAgICAgICAgYXZhaWxhYmxlV2lkdGggPSBhdmFpbGFibGVIZWlnaHQgKiBhc3BlY3RSYXRpbztcbiAgICB9XG5cbiAgICBpZiAoYXZhaWxhYmxlSGVpZ2h0ICogYXNwZWN0UmF0aW8gPj0gdmlkZW9TcGFjZVdpZHRoKVxuICAgIHtcbiAgICAgICAgYXZhaWxhYmxlV2lkdGggPSB2aWRlb1NwYWNlV2lkdGg7XG4gICAgICAgIGF2YWlsYWJsZUhlaWdodCA9IGF2YWlsYWJsZVdpZHRoIC8gYXNwZWN0UmF0aW87XG4gICAgfVxuXG4gICAgcmV0dXJuIFthdmFpbGFibGVXaWR0aCwgYXZhaWxhYmxlSGVpZ2h0XTtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIHRoZSBlZGl0IGRpc3BsYXkgbmFtZSBidXR0b24uXG4gKlxuICogQHJldHVybnMgdGhlIGVkaXQgYnV0dG9uXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZUVkaXREaXNwbGF5TmFtZUJ1dHRvbigpIHtcbiAgICB2YXIgZWRpdEJ1dHRvbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2EnKTtcbiAgICBlZGl0QnV0dG9uLmNsYXNzTmFtZSA9ICdkaXNwbGF5bmFtZSc7XG4gICAgVXRpbC5zZXRUb29sdGlwKGVkaXRCdXR0b24sXG4gICAgICAgICdDbGljayB0byBlZGl0IHlvdXI8YnIvPmRpc3BsYXkgbmFtZScsXG4gICAgICAgIFwidG9wXCIpO1xuICAgIGVkaXRCdXR0b24uaW5uZXJIVE1MID0gJzxpIGNsYXNzPVwiZmEgZmEtcGVuY2lsXCI+PC9pPic7XG5cbiAgICByZXR1cm4gZWRpdEJ1dHRvbjtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIHRoZSBlbGVtZW50IGluZGljYXRpbmcgdGhlIG1vZGVyYXRvcihvd25lcikgb2YgdGhlIGNvbmZlcmVuY2UuXG4gKlxuICogQHBhcmFtIHBhcmVudEVsZW1lbnQgdGhlIHBhcmVudCBlbGVtZW50IHdoZXJlIHRoZSBvd25lciBpbmRpY2F0b3Igd2lsbFxuICogYmUgYWRkZWRcbiAqL1xuZnVuY3Rpb24gY3JlYXRlTW9kZXJhdG9ySW5kaWNhdG9yRWxlbWVudChwYXJlbnRFbGVtZW50KSB7XG4gICAgdmFyIG1vZGVyYXRvckluZGljYXRvciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2knKTtcbiAgICBtb2RlcmF0b3JJbmRpY2F0b3IuY2xhc3NOYW1lID0gJ2ZhIGZhLXN0YXInO1xuICAgIHBhcmVudEVsZW1lbnQuYXBwZW5kQ2hpbGQobW9kZXJhdG9ySW5kaWNhdG9yKTtcblxuICAgIFV0aWwuc2V0VG9vbHRpcChwYXJlbnRFbGVtZW50LFxuICAgICAgICBcIlRoZSBvd25lciBvZjxici8+dGhpcyBjb25mZXJlbmNlXCIsXG4gICAgICAgIFwidG9wXCIpO1xufVxuXG5cbi8qKlxuICogQ2hlY2tzIGlmIHZpZGVvIGlkZW50aWZpZWQgYnkgZ2l2ZW4gc3JjIGlzIGRlc2t0b3Agc3RyZWFtLlxuICogQHBhcmFtIHZpZGVvU3JjIGVnLlxuICogYmxvYjpodHRwcyUzQS8vcGF3ZWwuaml0c2kubmV0LzlhNDZlMGJkLTEzMWUtNGQxOC05YzE0LWE5MjY0ZThkYjM5NVxuICogQHJldHVybnMge2Jvb2xlYW59XG4gKi9cbmZ1bmN0aW9uIGlzVmlkZW9TcmNEZXNrdG9wKGppZCkge1xuICAgIC8vIEZJWE1FOiBmaXggdGhpcyBtYXBwaW5nIG1lc3MuLi5cbiAgICAvLyBmaWd1cmUgb3V0IGlmIGxhcmdlIHZpZGVvIGlzIGRlc2t0b3Agc3RyZWFtIG9yIGp1c3QgYSBjYW1lcmFcblxuICAgIGlmKCFqaWQpXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB2YXIgaXNEZXNrdG9wID0gZmFsc2U7XG4gICAgaWYgKHhtcHAubXlKaWQoKSAmJlxuICAgICAgICB4bXBwLm15UmVzb3VyY2UoKSA9PT0gamlkKSB7XG4gICAgICAgIC8vIGxvY2FsIHZpZGVvXG4gICAgICAgIGlzRGVza3RvcCA9IGRlc2t0b3BzaGFyaW5nLmlzVXNpbmdTY3JlZW5TdHJlYW0oKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICAvLyBEbyB3ZSBoYXZlIGFzc29jaWF0aW9ucy4uLlxuICAgICAgICB2YXIgdmlkZW9Tc3JjID0gamlkMlNzcmNbamlkXTtcbiAgICAgICAgaWYgKHZpZGVvU3NyYykge1xuICAgICAgICAgICAgdmFyIHZpZGVvVHlwZSA9IHNzcmMydmlkZW9UeXBlW3ZpZGVvU3NyY107XG4gICAgICAgICAgICBpZiAodmlkZW9UeXBlKSB7XG4gICAgICAgICAgICAgICAgLy8gRmluYWxseSB0aGVyZS4uLlxuICAgICAgICAgICAgICAgIGlzRGVza3RvcCA9IHZpZGVvVHlwZSA9PT0gJ3NjcmVlbic7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJObyB2aWRlbyB0eXBlIGZvciBzc3JjOiBcIiArIHZpZGVvU3NyYyk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKFwiTm8gc3NyYyBmb3IgamlkOiBcIiArIGppZCk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGlzRGVza3RvcDtcbn1cblxuXG5cbnZhciBWaWRlb0xheW91dCA9IChmdW5jdGlvbiAobXkpIHtcbiAgICBteS5jb25uZWN0aW9uSW5kaWNhdG9ycyA9IHt9O1xuXG4gICAgLy8gQnkgZGVmYXVsdCB3ZSB1c2UgY2FtZXJhXG4gICAgbXkuZ2V0VmlkZW9TaXplID0gZ2V0Q2FtZXJhVmlkZW9TaXplO1xuICAgIG15LmdldFZpZGVvUG9zaXRpb24gPSBnZXRDYW1lcmFWaWRlb1Bvc2l0aW9uO1xuXG4gICAgbXkuaW5pdCA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgLy8gTGlzdGVuIGZvciBsYXJnZSB2aWRlbyBzaXplIHVwZGF0ZXNcbiAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhcmdlVmlkZW8nKVxuICAgICAgICAgICAgLmFkZEV2ZW50TGlzdGVuZXIoJ2xvYWRlZG1ldGFkYXRhJywgZnVuY3Rpb24gKGUpIHtcbiAgICAgICAgICAgICAgICBjdXJyZW50VmlkZW9XaWR0aCA9IHRoaXMudmlkZW9XaWR0aDtcbiAgICAgICAgICAgICAgICBjdXJyZW50VmlkZW9IZWlnaHQgPSB0aGlzLnZpZGVvSGVpZ2h0O1xuICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LnBvc2l0aW9uTGFyZ2UoY3VycmVudFZpZGVvV2lkdGgsIGN1cnJlbnRWaWRlb0hlaWdodCk7XG4gICAgICAgICAgICB9KTtcbiAgICB9O1xuXG4gICAgbXkuaXNJbkxhc3ROID0gZnVuY3Rpb24ocmVzb3VyY2UpIHtcbiAgICAgICAgcmV0dXJuIGxhc3ROQ291bnQgPCAwIC8vIGxhc3ROIGlzIGRpc2FibGVkLCByZXR1cm4gdHJ1ZVxuICAgICAgICAgICAgfHwgKGxhc3ROQ291bnQgPiAwICYmIGxhc3RORW5kcG9pbnRzQ2FjaGUubGVuZ3RoID09IDApIC8vIGxhc3RORW5kcG9pbnRzIGNhY2hlIG5vdCBidWlsdCB5ZXQsIHJldHVybiB0cnVlXG4gICAgICAgICAgICB8fCAobGFzdE5FbmRwb2ludHNDYWNoZSAmJiBsYXN0TkVuZHBvaW50c0NhY2hlLmluZGV4T2YocmVzb3VyY2UpICE9PSAtMSk7XG4gICAgfTtcblxuICAgIG15LmNoYW5nZUxvY2FsU3RyZWFtID0gZnVuY3Rpb24gKHN0cmVhbSkge1xuICAgICAgICBWaWRlb0xheW91dC5jaGFuZ2VMb2NhbFZpZGVvKHN0cmVhbSk7XG4gICAgfTtcblxuICAgIG15LmNoYW5nZUxvY2FsQXVkaW8gPSBmdW5jdGlvbihzdHJlYW0pIHtcbiAgICAgICAgUlRDLmF0dGFjaE1lZGlhU3RyZWFtKCQoJyNsb2NhbEF1ZGlvJyksIHN0cmVhbS5nZXRPcmlnaW5hbFN0cmVhbSgpKTtcbiAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvY2FsQXVkaW8nKS5hdXRvcGxheSA9IHRydWU7XG4gICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsb2NhbEF1ZGlvJykudm9sdW1lID0gMDtcbiAgICAgICAgaWYgKHByZU11dGVkKSB7XG4gICAgICAgICAgICBpZighVUkuc2V0QXVkaW9NdXRlZCh0cnVlKSlcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBwcmVNdXRlZCA9IG11dGU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBwcmVNdXRlZCA9IGZhbHNlO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIG15LmNoYW5nZUxvY2FsVmlkZW8gPSBmdW5jdGlvbihzdHJlYW0pIHtcbiAgICAgICAgdmFyIGZsaXBYID0gdHJ1ZTtcbiAgICAgICAgaWYoc3RyZWFtLnR5cGUgPT0gXCJkZXNrdG9wXCIpXG4gICAgICAgICAgICBmbGlwWCA9IGZhbHNlO1xuICAgICAgICB2YXIgbG9jYWxWaWRlbyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3ZpZGVvJyk7XG4gICAgICAgIGxvY2FsVmlkZW8uaWQgPSAnbG9jYWxWaWRlb18nICtcbiAgICAgICAgICAgIFJUQy5nZXRTdHJlYW1JRChzdHJlYW0uZ2V0T3JpZ2luYWxTdHJlYW0oKSk7XG4gICAgICAgIGxvY2FsVmlkZW8uYXV0b3BsYXkgPSB0cnVlO1xuICAgICAgICBsb2NhbFZpZGVvLnZvbHVtZSA9IDA7IC8vIGlzIGl0IHJlcXVpcmVkIGlmIGF1ZGlvIGlzIHNlcGFyYXRlZCA/XG4gICAgICAgIGxvY2FsVmlkZW8ub25jb250ZXh0bWVudSA9IGZ1bmN0aW9uICgpIHsgcmV0dXJuIGZhbHNlOyB9O1xuXG4gICAgICAgIHZhciBsb2NhbFZpZGVvQ29udGFpbmVyID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvY2FsVmlkZW9XcmFwcGVyJyk7XG4gICAgICAgIGxvY2FsVmlkZW9Db250YWluZXIuYXBwZW5kQ2hpbGQobG9jYWxWaWRlbyk7XG5cbiAgICAgICAgLy8gU2V0IGRlZmF1bHQgZGlzcGxheSBuYW1lLlxuICAgICAgICBzZXREaXNwbGF5TmFtZSgnbG9jYWxWaWRlb0NvbnRhaW5lcicpO1xuXG4gICAgICAgIGlmKCFWaWRlb0xheW91dC5jb25uZWN0aW9uSW5kaWNhdG9yc1tcImxvY2FsVmlkZW9Db250YWluZXJcIl0pIHtcbiAgICAgICAgICAgIFZpZGVvTGF5b3V0LmNvbm5lY3Rpb25JbmRpY2F0b3JzW1wibG9jYWxWaWRlb0NvbnRhaW5lclwiXVxuICAgICAgICAgICAgICAgID0gbmV3IENvbm5lY3Rpb25JbmRpY2F0b3IoJChcIiNsb2NhbFZpZGVvQ29udGFpbmVyXCIpWzBdLCBudWxsLCBWaWRlb0xheW91dCk7XG4gICAgICAgIH1cblxuICAgICAgICBBdWRpb0xldmVscy51cGRhdGVBdWRpb0xldmVsQ2FudmFzKG51bGwsIFZpZGVvTGF5b3V0KTtcblxuICAgICAgICB2YXIgbG9jYWxWaWRlb1NlbGVjdG9yID0gJCgnIycgKyBsb2NhbFZpZGVvLmlkKTtcbiAgICAgICAgLy8gQWRkIGNsaWNrIGhhbmRsZXIgdG8gYm90aCB2aWRlbyBhbmQgdmlkZW8gd3JhcHBlciBlbGVtZW50cyBpbiBjYXNlXG4gICAgICAgIC8vIHRoZXJlJ3Mgbm8gdmlkZW8uXG4gICAgICAgIGxvY2FsVmlkZW9TZWxlY3Rvci5jbGljayhmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICAgICAgVmlkZW9MYXlvdXQuaGFuZGxlVmlkZW9UaHVtYkNsaWNrZWQoXG4gICAgICAgICAgICAgICAgUlRDLmdldFZpZGVvU3JjKGxvY2FsVmlkZW8pLFxuICAgICAgICAgICAgICAgIGZhbHNlLFxuICAgICAgICAgICAgICAgIHhtcHAubXlSZXNvdXJjZSgpKTtcbiAgICAgICAgfSk7XG4gICAgICAgICQoJyNsb2NhbFZpZGVvQ29udGFpbmVyJykuY2xpY2soZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgICAgICBldmVudC5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICAgICAgICAgIFZpZGVvTGF5b3V0LmhhbmRsZVZpZGVvVGh1bWJDbGlja2VkKFxuICAgICAgICAgICAgICAgIFJUQy5nZXRWaWRlb1NyYyhsb2NhbFZpZGVvKSxcbiAgICAgICAgICAgICAgICBmYWxzZSxcbiAgICAgICAgICAgICAgICB4bXBwLm15UmVzb3VyY2UoKSk7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIEFkZCBob3ZlciBoYW5kbGVyXG4gICAgICAgICQoJyNsb2NhbFZpZGVvQ29udGFpbmVyJykuaG92ZXIoXG4gICAgICAgICAgICBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgICAgICBWaWRlb0xheW91dC5zaG93RGlzcGxheU5hbWUoJ2xvY2FsVmlkZW9Db250YWluZXInLCB0cnVlKTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgICAgICBpZiAoIVZpZGVvTGF5b3V0LmlzTGFyZ2VWaWRlb1Zpc2libGUoKVxuICAgICAgICAgICAgICAgICAgICAgICAgfHwgUlRDLmdldFZpZGVvU3JjKGxvY2FsVmlkZW8pICE9PSBSVEMuZ2V0VmlkZW9TcmMoJCgnI2xhcmdlVmlkZW8nKVswXSkpXG4gICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LnNob3dEaXNwbGF5TmFtZSgnbG9jYWxWaWRlb0NvbnRhaW5lcicsIGZhbHNlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgKTtcbiAgICAgICAgLy8gQWRkIHN0cmVhbSBlbmRlZCBoYW5kbGVyXG4gICAgICAgIHN0cmVhbS5nZXRPcmlnaW5hbFN0cmVhbSgpLm9uZW5kZWQgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBsb2NhbFZpZGVvQ29udGFpbmVyLnJlbW92ZUNoaWxkKGxvY2FsVmlkZW8pO1xuICAgICAgICAgICAgVmlkZW9MYXlvdXQudXBkYXRlUmVtb3ZlZFZpZGVvKFJUQy5nZXRWaWRlb1NyYyhsb2NhbFZpZGVvKSk7XG4gICAgICAgIH07XG4gICAgICAgIC8vIEZsaXAgdmlkZW8geCBheGlzIGlmIG5lZWRlZFxuICAgICAgICBmbGlwWExvY2FsVmlkZW8gPSBmbGlwWDtcbiAgICAgICAgaWYgKGZsaXBYKSB7XG4gICAgICAgICAgICBsb2NhbFZpZGVvU2VsZWN0b3IuYWRkQ2xhc3MoXCJmbGlwVmlkZW9YXCIpO1xuICAgICAgICB9XG4gICAgICAgIC8vIEF0dGFjaCBXZWJSVEMgc3RyZWFtXG4gICAgICAgIHZhciB2aWRlb1N0cmVhbSA9IHNpbXVsY2FzdC5nZXRMb2NhbFZpZGVvU3RyZWFtKCk7XG4gICAgICAgIFJUQy5hdHRhY2hNZWRpYVN0cmVhbShsb2NhbFZpZGVvU2VsZWN0b3IsIHZpZGVvU3RyZWFtKTtcblxuICAgICAgICBsb2NhbFZpZGVvU3JjID0gUlRDLmdldFZpZGVvU3JjKGxvY2FsVmlkZW8pO1xuXG4gICAgICAgIHZhciBteVJlc291cmNlSmlkID0geG1wcC5teVJlc291cmNlKCk7XG5cbiAgICAgICAgVmlkZW9MYXlvdXQudXBkYXRlTGFyZ2VWaWRlbyhsb2NhbFZpZGVvU3JjLCAwLFxuICAgICAgICAgICAgbXlSZXNvdXJjZUppZCk7XG5cbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogQ2hlY2tzIGlmIHJlbW92ZWQgdmlkZW8gaXMgY3VycmVudGx5IGRpc3BsYXllZCBhbmQgdHJpZXMgdG8gZGlzcGxheVxuICAgICAqIGFub3RoZXIgb25lIGluc3RlYWQuXG4gICAgICogQHBhcmFtIHJlbW92ZWRWaWRlb1NyYyBzcmMgc3RyZWFtIGlkZW50aWZpZXIgb2YgdGhlIHZpZGVvLlxuICAgICAqL1xuICAgIG15LnVwZGF0ZVJlbW92ZWRWaWRlbyA9IGZ1bmN0aW9uKHJlbW92ZWRWaWRlb1NyYykge1xuICAgICAgICBpZiAocmVtb3ZlZFZpZGVvU3JjID09PSBSVEMuZ2V0VmlkZW9TcmMoJCgnI2xhcmdlVmlkZW8nKVswXSkpIHtcbiAgICAgICAgICAgIC8vIHRoaXMgaXMgY3VycmVudGx5IGRpc3BsYXllZCBhcyBsYXJnZVxuICAgICAgICAgICAgLy8gcGljayB0aGUgbGFzdCB2aXNpYmxlIHZpZGVvIGluIHRoZSByb3dcbiAgICAgICAgICAgIC8vIGlmIG5vYm9keSBlbHNlIGlzIGxlZnQsIHRoaXMgcGlja3MgdGhlIGxvY2FsIHZpZGVvXG4gICAgICAgICAgICB2YXIgcGlja1xuICAgICAgICAgICAgICAgID0gJCgnI3JlbW90ZVZpZGVvcz5zcGFuW2lkIT1cIm1peGVkc3RyZWFtXCJdOnZpc2libGU6bGFzdD52aWRlbycpXG4gICAgICAgICAgICAgICAgICAgIC5nZXQoMCk7XG5cbiAgICAgICAgICAgIGlmICghcGljaykge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUuaW5mbyhcIkxhc3QgdmlzaWJsZSB2aWRlbyBubyBsb25nZXIgZXhpc3RzXCIpO1xuICAgICAgICAgICAgICAgIHBpY2sgPSAkKCcjcmVtb3RlVmlkZW9zPnNwYW5baWQhPVwibWl4ZWRzdHJlYW1cIl0+dmlkZW8nKS5nZXQoMCk7XG5cbiAgICAgICAgICAgICAgICBpZiAoIXBpY2sgfHwgIVJUQy5nZXRWaWRlb1NyYyhwaWNrKSkge1xuICAgICAgICAgICAgICAgICAgICAvLyBUcnkgbG9jYWwgdmlkZW9cbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5pbmZvKFwiRmFsbGJhY2sgdG8gbG9jYWwgdmlkZW8uLi5cIik7XG4gICAgICAgICAgICAgICAgICAgIHBpY2sgPSAkKCcjcmVtb3RlVmlkZW9zPnNwYW4+c3Bhbj52aWRlbycpLmdldCgwKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIG11dGUgaWYgbG9jYWx2aWRlb1xuICAgICAgICAgICAgaWYgKHBpY2spIHtcbiAgICAgICAgICAgICAgICB2YXIgY29udGFpbmVyID0gcGljay5wYXJlbnROb2RlO1xuICAgICAgICAgICAgICAgIHZhciBqaWQgPSBudWxsO1xuICAgICAgICAgICAgICAgIGlmKGNvbnRhaW5lcilcbiAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgIGlmKGNvbnRhaW5lci5pZCA9PSBcImxvY2FsVmlkZW9XcmFwcGVyXCIpXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGppZCA9IHhtcHAubXlSZXNvdXJjZSgpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICAgICAgamlkID0gVmlkZW9MYXlvdXQuZ2V0UGVlckNvbnRhaW5lclJlc291cmNlSmlkKGNvbnRhaW5lcik7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBWaWRlb0xheW91dC51cGRhdGVMYXJnZVZpZGVvKFJUQy5nZXRWaWRlb1NyYyhwaWNrKSwgcGljay52b2x1bWUsIGppZCk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybihcIkZhaWxlZCB0byBlbGVjdCBsYXJnZSB2aWRlb1wiKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH07XG4gICAgXG4gICAgbXkub25SZW1vdGVTdHJlYW1BZGRlZCA9IGZ1bmN0aW9uIChzdHJlYW0pIHtcbiAgICAgICAgdmFyIGNvbnRhaW5lcjtcbiAgICAgICAgdmFyIHJlbW90ZXMgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgncmVtb3RlVmlkZW9zJyk7XG5cbiAgICAgICAgaWYgKHN0cmVhbS5wZWVyamlkKSB7XG4gICAgICAgICAgICBWaWRlb0xheW91dC5lbnN1cmVQZWVyQ29udGFpbmVyRXhpc3RzKHN0cmVhbS5wZWVyamlkKTtcblxuICAgICAgICAgICAgY29udGFpbmVyICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKFxuICAgICAgICAgICAgICAgICAgICAncGFydGljaXBhbnRfJyArIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKHN0cmVhbS5wZWVyamlkKSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB2YXIgaWQgPSBzdHJlYW0uZ2V0T3JpZ2luYWxTdHJlYW0oKS5pZDtcbiAgICAgICAgICAgIGlmIChpZCAhPT0gJ21peGVkbXNsYWJlbCdcbiAgICAgICAgICAgICAgICAvLyBGSVhNRTogZGVmYXVsdCBzdHJlYW0gaXMgYWRkZWQgYWx3YXlzIHdpdGggbmV3IGZvY3VzXG4gICAgICAgICAgICAgICAgLy8gKHRvIGJlIGludmVzdGlnYXRlZClcbiAgICAgICAgICAgICAgICAmJiBpZCAhPT0gJ2RlZmF1bHQnKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS5lcnJvcignY2FuIG5vdCBhc3NvY2lhdGUgc3RyZWFtJyxcbiAgICAgICAgICAgICAgICAgICAgaWQsXG4gICAgICAgICAgICAgICAgICAgICd3aXRoIGEgcGFydGljaXBhbnQnKTtcbiAgICAgICAgICAgICAgICAvLyBXZSBkb24ndCB3YW50IHRvIGFkZCBpdCBoZXJlIHNpbmNlIGl0IHdpbGwgY2F1c2UgdHJvdWJsZXNcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBGSVhNRTogZm9yIHRoZSBtaXhlZCBtcyB3ZSBkb250IG5lZWQgYSB2aWRlbyAtLSBjdXJyZW50bHlcbiAgICAgICAgICAgIGNvbnRhaW5lciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NwYW4nKTtcbiAgICAgICAgICAgIGNvbnRhaW5lci5pZCA9ICdtaXhlZHN0cmVhbSc7XG4gICAgICAgICAgICBjb250YWluZXIuY2xhc3NOYW1lID0gJ3ZpZGVvY29udGFpbmVyJztcbiAgICAgICAgICAgIHJlbW90ZXMuYXBwZW5kQ2hpbGQoY29udGFpbmVyKTtcbiAgICAgICAgICAgIFV0aWwucGxheVNvdW5kTm90aWZpY2F0aW9uKCd1c2VySm9pbmVkJyk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoY29udGFpbmVyKSB7XG4gICAgICAgICAgICBWaWRlb0xheW91dC5hZGRSZW1vdGVTdHJlYW1FbGVtZW50KCBjb250YWluZXIsXG4gICAgICAgICAgICAgICAgc3RyZWFtLnNpZCxcbiAgICAgICAgICAgICAgICBzdHJlYW0uZ2V0T3JpZ2luYWxTdHJlYW0oKSxcbiAgICAgICAgICAgICAgICBzdHJlYW0ucGVlcmppZCxcbiAgICAgICAgICAgICAgICBzdHJlYW0uc3NyYyk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBteS5nZXRMYXJnZVZpZGVvU3RhdGUgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBsYXJnZVZpZGVvU3RhdGU7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIFVwZGF0ZXMgdGhlIGxhcmdlIHZpZGVvIHdpdGggdGhlIGdpdmVuIG5ldyB2aWRlbyBzb3VyY2UuXG4gICAgICovXG4gICAgbXkudXBkYXRlTGFyZ2VWaWRlbyA9IGZ1bmN0aW9uKG5ld1NyYywgdm9sLCByZXNvdXJjZUppZCkge1xuICAgICAgICBjb25zb2xlLmxvZygnaG92ZXIgaW4nLCBuZXdTcmMpO1xuXG4gICAgICAgIGlmIChSVEMuZ2V0VmlkZW9TcmMoJCgnI2xhcmdlVmlkZW8nKVswXSkgIT09IG5ld1NyYykge1xuXG4gICAgICAgICAgICAkKCcjYWN0aXZlU3BlYWtlcicpLmNzcygndmlzaWJpbGl0eScsICdoaWRkZW4nKTtcbiAgICAgICAgICAgIC8vIER1ZSB0byB0aGUgc2ltdWxjYXN0IHRoZSBsb2NhbFZpZGVvU3JjIG1heSBoYXZlIGNoYW5nZWQgd2hlbiB0aGVcbiAgICAgICAgICAgIC8vIGZhZGVPdXQgZXZlbnQgdHJpZ2dlcnMuIEluIHRoYXQgY2FzZSB0aGUgZ2V0SmlkRnJvbVZpZGVvU3JjIGFuZFxuICAgICAgICAgICAgLy8gaXNWaWRlb1NyY0Rlc2t0b3AgbWV0aG9kcyB3aWxsIG5vdCBmdW5jdGlvbiBjb3JyZWN0bHkuXG4gICAgICAgICAgICAvL1xuICAgICAgICAgICAgLy8gQWxzbywgYWdhaW4gZHVlIHRvIHRoZSBzaW11bGNhc3QsIHRoZSB1cGRhdGVMYXJnZVZpZGVvIG1ldGhvZCBjYW5cbiAgICAgICAgICAgIC8vIGJlIGNhbGxlZCBtdWx0aXBsZSB0aW1lcyBhbG1vc3Qgc2ltdWx0YW5lb3VzbHkuIFRoZXJlZm9yZSwgd2VcbiAgICAgICAgICAgIC8vIHN0b3JlIHRoZSBzdGF0ZSBoZXJlIGFuZCB1cGRhdGUgb25seSBvbmNlLlxuXG4gICAgICAgICAgICBsYXJnZVZpZGVvU3RhdGUubmV3U3JjID0gbmV3U3JjO1xuICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLmlzVmlzaWJsZSA9ICQoJyNsYXJnZVZpZGVvJykuaXMoJzp2aXNpYmxlJyk7XG4gICAgICAgICAgICBsYXJnZVZpZGVvU3RhdGUuaXNEZXNrdG9wID0gaXNWaWRlb1NyY0Rlc2t0b3AocmVzb3VyY2VKaWQpO1xuICAgICAgICAgICAgaWYoamlkMlNzcmNbbGFyZ2VWaWRlb1N0YXRlLnVzZXJSZXNvdXJjZUppZF0gfHxcbiAgICAgICAgICAgICAgICAoeG1wcC5teVJlc291cmNlKCkgJiZcbiAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLnVzZXJSZXNvdXJjZUppZCA9PT1cbiAgICAgICAgICAgICAgICAgICAgeG1wcC5teVJlc291cmNlKCkpKSB7XG4gICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLm9sZFJlc291cmNlSmlkID0gbGFyZ2VWaWRlb1N0YXRlLnVzZXJSZXNvdXJjZUppZDtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLm9sZFJlc291cmNlSmlkID0gbnVsbDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxhcmdlVmlkZW9TdGF0ZS51c2VyUmVzb3VyY2VKaWQgPSByZXNvdXJjZUppZDtcblxuICAgICAgICAgICAgLy8gU2NyZWVuIHN0cmVhbSBpcyBhbHJlYWR5IHJvdGF0ZWRcbiAgICAgICAgICAgIGxhcmdlVmlkZW9TdGF0ZS5mbGlwWCA9IChuZXdTcmMgPT09IGxvY2FsVmlkZW9TcmMpICYmIGZsaXBYTG9jYWxWaWRlbztcblxuICAgICAgICAgICAgdmFyIHVzZXJDaGFuZ2VkID0gZmFsc2U7XG4gICAgICAgICAgICBpZiAobGFyZ2VWaWRlb1N0YXRlLm9sZFJlc291cmNlSmlkICE9PSBsYXJnZVZpZGVvU3RhdGUudXNlclJlc291cmNlSmlkKSB7XG4gICAgICAgICAgICAgICAgdXNlckNoYW5nZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgIC8vIHdlIHdhbnQgdGhlIG5vdGlmaWNhdGlvbiB0byB0cmlnZ2VyIGV2ZW4gaWYgdXNlckppZCBpcyB1bmRlZmluZWQsXG4gICAgICAgICAgICAgICAgLy8gb3IgbnVsbC5cbiAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKFwic2VsZWN0ZWRlbmRwb2ludGNoYW5nZWRcIiwgW2xhcmdlVmlkZW9TdGF0ZS51c2VyUmVzb3VyY2VKaWRdKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFsYXJnZVZpZGVvU3RhdGUudXBkYXRlSW5Qcm9ncmVzcykge1xuICAgICAgICAgICAgICAgIGxhcmdlVmlkZW9TdGF0ZS51cGRhdGVJblByb2dyZXNzID0gdHJ1ZTtcblxuICAgICAgICAgICAgICAgIHZhciBkb1VwZGF0ZSA9IGZ1bmN0aW9uICgpIHtcblxuICAgICAgICAgICAgICAgICAgICBBdmF0YXIudXBkYXRlQWN0aXZlU3BlYWtlckF2YXRhclNyYyhcbiAgICAgICAgICAgICAgICAgICAgICAgIHhtcHAuZmluZEppZEZyb21SZXNvdXJjZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXJnZVZpZGVvU3RhdGUudXNlclJlc291cmNlSmlkKSk7XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKCF1c2VyQ2hhbmdlZCAmJiBsYXJnZVZpZGVvU3RhdGUucHJlbG9hZCAmJlxuICAgICAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLnByZWxvYWQgIT09IG51bGwgJiZcbiAgICAgICAgICAgICAgICAgICAgICAgIFJUQy5nZXRWaWRlb1NyYygkKGxhcmdlVmlkZW9TdGF0ZS5wcmVsb2FkKVswXSkgPT09IG5ld1NyYylcbiAgICAgICAgICAgICAgICAgICAge1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmluZm8oJ1N3aXRjaGluZyB0byBwcmVsb2FkZWQgdmlkZW8nKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBhdHRyaWJ1dGVzID0gJCgnI2xhcmdlVmlkZW8nKS5wcm9wKFwiYXR0cmlidXRlc1wiKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gbG9vcCB0aHJvdWdoIGxhcmdlVmlkZW8gYXR0cmlidXRlcyBhbmQgYXBwbHkgdGhlbSBvblxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gcHJlbG9hZC5cbiAgICAgICAgICAgICAgICAgICAgICAgICQuZWFjaChhdHRyaWJ1dGVzLCBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXMubmFtZSAhPT0gJ2lkJyAmJiB0aGlzLm5hbWUgIT09ICdzcmMnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhcmdlVmlkZW9TdGF0ZS5wcmVsb2FkLmF0dHIodGhpcy5uYW1lLCB0aGlzLnZhbHVlKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLnByZWxvYWQuYXBwZW5kVG8oJCgnI2xhcmdlVmlkZW9Db250YWluZXInKSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAkKCcjbGFyZ2VWaWRlbycpLmF0dHIoJ2lkJywgJ3ByZXZpb3VzTGFyZ2VWaWRlbycpO1xuICAgICAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLnByZWxvYWQuYXR0cignaWQnLCAnbGFyZ2VWaWRlbycpO1xuICAgICAgICAgICAgICAgICAgICAgICAgJCgnI3ByZXZpb3VzTGFyZ2VWaWRlbycpLnJlbW92ZSgpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBsYXJnZVZpZGVvU3RhdGUucHJlbG9hZC5vbignbG9hZGVkbWV0YWRhdGEnLCBmdW5jdGlvbiAoZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1cnJlbnRWaWRlb1dpZHRoID0gdGhpcy52aWRlb1dpZHRoO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1cnJlbnRWaWRlb0hlaWdodCA9IHRoaXMudmlkZW9IZWlnaHQ7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgVmlkZW9MYXlvdXQucG9zaXRpb25MYXJnZShjdXJyZW50VmlkZW9XaWR0aCwgY3VycmVudFZpZGVvSGVpZ2h0KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLnByZWxvYWQgPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLnByZWxvYWRfc3NyYyA9IDA7XG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBSVEMuc2V0VmlkZW9TcmMoJCgnI2xhcmdlVmlkZW8nKVswXSwgbGFyZ2VWaWRlb1N0YXRlLm5ld1NyYyk7XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICB2YXIgdmlkZW9UcmFuc2Zvcm0gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbGFyZ2VWaWRlbycpXG4gICAgICAgICAgICAgICAgICAgICAgICAuc3R5bGUud2Via2l0VHJhbnNmb3JtO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChsYXJnZVZpZGVvU3RhdGUuZmxpcFggJiYgdmlkZW9UcmFuc2Zvcm0gIT09ICdzY2FsZVgoLTEpJykge1xuICAgICAgICAgICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhcmdlVmlkZW8nKS5zdHlsZS53ZWJraXRUcmFuc2Zvcm1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICA9IFwic2NhbGVYKC0xKVwiO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKCFsYXJnZVZpZGVvU3RhdGUuZmxpcFggJiYgdmlkZW9UcmFuc2Zvcm0gPT09ICdzY2FsZVgoLTEpJykge1xuICAgICAgICAgICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xhcmdlVmlkZW8nKS5zdHlsZS53ZWJraXRUcmFuc2Zvcm1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICA9IFwibm9uZVwiO1xuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gQ2hhbmdlIHRoZSB3YXkgd2UnbGwgYmUgbWVhc3VyaW5nIGFuZCBwb3NpdGlvbmluZyBsYXJnZSB2aWRlb1xuXG4gICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LmdldFZpZGVvU2l6ZSA9IGxhcmdlVmlkZW9TdGF0ZS5pc0Rlc2t0b3BcbiAgICAgICAgICAgICAgICAgICAgICAgID8gZ2V0RGVza3RvcFZpZGVvU2l6ZVxuICAgICAgICAgICAgICAgICAgICAgICAgOiBnZXRDYW1lcmFWaWRlb1NpemU7XG4gICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LmdldFZpZGVvUG9zaXRpb24gPSBsYXJnZVZpZGVvU3RhdGUuaXNEZXNrdG9wXG4gICAgICAgICAgICAgICAgICAgICAgICA/IGdldERlc2t0b3BWaWRlb1Bvc2l0aW9uXG4gICAgICAgICAgICAgICAgICAgICAgICA6IGdldENhbWVyYVZpZGVvUG9zaXRpb247XG5cblxuICAgICAgICAgICAgICAgICAgICAvLyBPbmx5IGlmIHRoZSBsYXJnZSB2aWRlbyBpcyBjdXJyZW50bHkgdmlzaWJsZS5cbiAgICAgICAgICAgICAgICAgICAgLy8gRGlzYWJsZSBwcmV2aW91cyBkb21pbmFudCBzcGVha2VyIHZpZGVvLlxuICAgICAgICAgICAgICAgICAgICBpZiAobGFyZ2VWaWRlb1N0YXRlLm9sZFJlc291cmNlSmlkKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBWaWRlb0xheW91dC5lbmFibGVEb21pbmFudFNwZWFrZXIoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLm9sZFJlc291cmNlSmlkLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbHNlKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIC8vIEVuYWJsZSBuZXcgZG9taW5hbnQgc3BlYWtlciBpbiB0aGUgcmVtb3RlIHZpZGVvcyBzZWN0aW9uLlxuICAgICAgICAgICAgICAgICAgICBpZiAobGFyZ2VWaWRlb1N0YXRlLnVzZXJSZXNvdXJjZUppZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgVmlkZW9MYXlvdXQuZW5hYmxlRG9taW5hbnRTcGVha2VyKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhcmdlVmlkZW9TdGF0ZS51c2VyUmVzb3VyY2VKaWQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJ1ZSk7XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICBpZiAodXNlckNoYW5nZWQgJiYgbGFyZ2VWaWRlb1N0YXRlLmlzVmlzaWJsZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gdXNpbmcgXCJ0aGlzXCIgc2hvdWxkIGJlIG9rIGJlY2F1c2Ugd2UncmUgY2FsbGVkXG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBmcm9tIHdpdGhpbiB0aGUgZmFkZU91dCBldmVudC5cbiAgICAgICAgICAgICAgICAgICAgICAgICQodGhpcykuZmFkZUluKDMwMCk7XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICBpZih1c2VyQ2hhbmdlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgQXZhdGFyLnNob3dVc2VyQXZhdGFyKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhtcHAuZmluZEppZEZyb21SZXNvdXJjZShcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLm9sZFJlc291cmNlSmlkKSk7XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICBsYXJnZVZpZGVvU3RhdGUudXBkYXRlSW5Qcm9ncmVzcyA9IGZhbHNlO1xuICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICBpZiAodXNlckNoYW5nZWQpIHtcbiAgICAgICAgICAgICAgICAgICAgJCgnI2xhcmdlVmlkZW8nKS5mYWRlT3V0KDMwMCwgZG9VcGRhdGUpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGRvVXBkYXRlKCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgQXZhdGFyLnNob3dVc2VyQXZhdGFyKFxuICAgICAgICAgICAgICAgIHhtcHAuZmluZEppZEZyb21SZXNvdXJjZShcbiAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLnVzZXJSZXNvdXJjZUppZCkpO1xuICAgICAgICB9XG5cbiAgICB9O1xuXG4gICAgbXkuaGFuZGxlVmlkZW9UaHVtYkNsaWNrZWQgPSBmdW5jdGlvbih2aWRlb1NyYyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vUGlubmVkRW5kcG9pbnRDaGFuZ2VkRXZlbnQsIFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzb3VyY2VKaWQpIHtcbiAgICAgICAgLy8gUmVzdG9yZSBzdHlsZSBmb3IgcHJldmlvdXNseSBmb2N1c2VkIHZpZGVvXG4gICAgICAgIHZhciBvbGRDb250YWluZXIgPSBudWxsO1xuICAgICAgICBpZihmb2N1c2VkVmlkZW9JbmZvKSB7XG4gICAgICAgICAgICB2YXIgZm9jdXNSZXNvdXJjZUppZCA9IGZvY3VzZWRWaWRlb0luZm8ucmVzb3VyY2VKaWQ7XG4gICAgICAgICAgICBvbGRDb250YWluZXIgPSBnZXRQYXJ0aWNpcGFudENvbnRhaW5lcihmb2N1c1Jlc291cmNlSmlkKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChvbGRDb250YWluZXIpIHtcbiAgICAgICAgICAgIG9sZENvbnRhaW5lci5yZW1vdmVDbGFzcyhcInZpZGVvQ29udGFpbmVyRm9jdXNlZFwiKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFVubG9jayBjdXJyZW50IGZvY3VzZWQuXG4gICAgICAgIGlmIChmb2N1c2VkVmlkZW9JbmZvICYmIGZvY3VzZWRWaWRlb0luZm8uc3JjID09PSB2aWRlb1NyYylcbiAgICAgICAge1xuICAgICAgICAgICAgZm9jdXNlZFZpZGVvSW5mbyA9IG51bGw7XG4gICAgICAgICAgICB2YXIgZG9taW5hbnRTcGVha2VyVmlkZW8gPSBudWxsO1xuICAgICAgICAgICAgLy8gRW5hYmxlIHRoZSBjdXJyZW50bHkgc2V0IGRvbWluYW50IHNwZWFrZXIuXG4gICAgICAgICAgICBpZiAoY3VycmVudERvbWluYW50U3BlYWtlcikge1xuICAgICAgICAgICAgICAgIGRvbWluYW50U3BlYWtlclZpZGVvXG4gICAgICAgICAgICAgICAgICAgID0gJCgnI3BhcnRpY2lwYW50XycgKyBjdXJyZW50RG9taW5hbnRTcGVha2VyICsgJz52aWRlbycpXG4gICAgICAgICAgICAgICAgICAgICAgICAuZ2V0KDApO1xuXG4gICAgICAgICAgICAgICAgaWYgKGRvbWluYW50U3BlYWtlclZpZGVvKSB7XG4gICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LnVwZGF0ZUxhcmdlVmlkZW8oXG4gICAgICAgICAgICAgICAgICAgICAgICBSVEMuZ2V0VmlkZW9TcmMoZG9taW5hbnRTcGVha2VyVmlkZW8pLFxuICAgICAgICAgICAgICAgICAgICAgICAgMSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGN1cnJlbnREb21pbmFudFNwZWFrZXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFub1Bpbm5lZEVuZHBvaW50Q2hhbmdlZEV2ZW50KSB7XG4gICAgICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcihcInBpbm5lZGVuZHBvaW50Y2hhbmdlZFwiKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIExvY2sgbmV3IHZpZGVvXG4gICAgICAgIGZvY3VzZWRWaWRlb0luZm8gPSB7XG4gICAgICAgICAgICBzcmM6IHZpZGVvU3JjLFxuICAgICAgICAgICAgcmVzb3VyY2VKaWQ6IHJlc291cmNlSmlkXG4gICAgICAgIH07XG5cbiAgICAgICAgLy8gVXBkYXRlIGZvY3VzZWQvcGlubmVkIGludGVyZmFjZS5cbiAgICAgICAgaWYgKHJlc291cmNlSmlkKVxuICAgICAgICB7XG4gICAgICAgICAgICB2YXIgY29udGFpbmVyID0gZ2V0UGFydGljaXBhbnRDb250YWluZXIocmVzb3VyY2VKaWQpO1xuICAgICAgICAgICAgY29udGFpbmVyLmFkZENsYXNzKFwidmlkZW9Db250YWluZXJGb2N1c2VkXCIpO1xuXG4gICAgICAgICAgICBpZiAoIW5vUGlubmVkRW5kcG9pbnRDaGFuZ2VkRXZlbnQpIHtcbiAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKFwicGlubmVkZW5kcG9pbnRjaGFuZ2VkXCIsIFtyZXNvdXJjZUppZF0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCQoJyNsYXJnZVZpZGVvJykuYXR0cignc3JjJykgPT09IHZpZGVvU3JjICYmXG4gICAgICAgICAgICBWaWRlb0xheW91dC5pc0xhcmdlVmlkZW9PblRvcCgpKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUcmlnZ2VycyBhIFwidmlkZW8uc2VsZWN0ZWRcIiBldmVudC4gVGhlIFwiZmFsc2VcIiBwYXJhbWV0ZXIgaW5kaWNhdGVzXG4gICAgICAgIC8vIHRoaXMgaXNuJ3QgYSBwcmV6aS5cbiAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcihcInZpZGVvLnNlbGVjdGVkXCIsIFtmYWxzZV0pO1xuXG4gICAgICAgIFZpZGVvTGF5b3V0LnVwZGF0ZUxhcmdlVmlkZW8odmlkZW9TcmMsIDEsIHJlc291cmNlSmlkKTtcblxuICAgICAgICAkKCdhdWRpbycpLmVhY2goZnVuY3Rpb24gKGlkeCwgZWwpIHtcbiAgICAgICAgICAgIGlmIChlbC5pZC5pbmRleE9mKCdtaXhlZG1zbGFiZWwnKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICBlbC52b2x1bWUgPSAwO1xuICAgICAgICAgICAgICAgIGVsLnZvbHVtZSA9IDE7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBQb3NpdGlvbnMgdGhlIGxhcmdlIHZpZGVvLlxuICAgICAqXG4gICAgICogQHBhcmFtIHZpZGVvV2lkdGggdGhlIHN0cmVhbSB2aWRlbyB3aWR0aFxuICAgICAqIEBwYXJhbSB2aWRlb0hlaWdodCB0aGUgc3RyZWFtIHZpZGVvIGhlaWdodFxuICAgICAqL1xuICAgIG15LnBvc2l0aW9uTGFyZ2UgPSBmdW5jdGlvbiAodmlkZW9XaWR0aCwgdmlkZW9IZWlnaHQpIHtcbiAgICAgICAgdmFyIHZpZGVvU3BhY2VXaWR0aCA9ICQoJyN2aWRlb3NwYWNlJykud2lkdGgoKTtcbiAgICAgICAgdmFyIHZpZGVvU3BhY2VIZWlnaHQgPSB3aW5kb3cuaW5uZXJIZWlnaHQ7XG5cbiAgICAgICAgdmFyIHZpZGVvU2l6ZSA9IFZpZGVvTGF5b3V0LmdldFZpZGVvU2l6ZSh2aWRlb1dpZHRoLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvSGVpZ2h0LFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvU3BhY2VXaWR0aCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2aWRlb1NwYWNlSGVpZ2h0KTtcblxuICAgICAgICB2YXIgbGFyZ2VWaWRlb1dpZHRoID0gdmlkZW9TaXplWzBdO1xuICAgICAgICB2YXIgbGFyZ2VWaWRlb0hlaWdodCA9IHZpZGVvU2l6ZVsxXTtcblxuICAgICAgICB2YXIgdmlkZW9Qb3NpdGlvbiA9IFZpZGVvTGF5b3V0LmdldFZpZGVvUG9zaXRpb24obGFyZ2VWaWRlb1dpZHRoLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb0hlaWdodCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvU3BhY2VXaWR0aCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpZGVvU3BhY2VIZWlnaHQpO1xuXG4gICAgICAgIHZhciBob3Jpem9udGFsSW5kZW50ID0gdmlkZW9Qb3NpdGlvblswXTtcbiAgICAgICAgdmFyIHZlcnRpY2FsSW5kZW50ID0gdmlkZW9Qb3NpdGlvblsxXTtcblxuICAgICAgICBwb3NpdGlvblZpZGVvKCQoJyNsYXJnZVZpZGVvJyksXG4gICAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1dpZHRoLFxuICAgICAgICAgICAgICAgICAgICAgIGxhcmdlVmlkZW9IZWlnaHQsXG4gICAgICAgICAgICAgICAgICAgICAgaG9yaXpvbnRhbEluZGVudCwgdmVydGljYWxJbmRlbnQpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTaG93cy9oaWRlcyB0aGUgbGFyZ2UgdmlkZW8uXG4gICAgICovXG4gICAgbXkuc2V0TGFyZ2VWaWRlb1Zpc2libGUgPSBmdW5jdGlvbihpc1Zpc2libGUpIHtcbiAgICAgICAgdmFyIHJlc291cmNlSmlkID0gbGFyZ2VWaWRlb1N0YXRlLnVzZXJSZXNvdXJjZUppZDtcblxuICAgICAgICBpZiAoaXNWaXNpYmxlKSB7XG4gICAgICAgICAgICAkKCcjbGFyZ2VWaWRlbycpLmNzcyh7dmlzaWJpbGl0eTogJ3Zpc2libGUnfSk7XG4gICAgICAgICAgICAkKCcud2F0ZXJtYXJrJykuY3NzKHt2aXNpYmlsaXR5OiAndmlzaWJsZSd9KTtcbiAgICAgICAgICAgIFZpZGVvTGF5b3V0LmVuYWJsZURvbWluYW50U3BlYWtlcihyZXNvdXJjZUppZCwgdHJ1ZSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAkKCcjbGFyZ2VWaWRlbycpLmNzcyh7dmlzaWJpbGl0eTogJ2hpZGRlbid9KTtcbiAgICAgICAgICAgICQoJyNhY3RpdmVTcGVha2VyJykuY3NzKCd2aXNpYmlsaXR5JywgJ2hpZGRlbicpO1xuICAgICAgICAgICAgJCgnLndhdGVybWFyaycpLmNzcyh7dmlzaWJpbGl0eTogJ2hpZGRlbid9KTtcbiAgICAgICAgICAgIFZpZGVvTGF5b3V0LmVuYWJsZURvbWluYW50U3BlYWtlcihyZXNvdXJjZUppZCwgZmFsc2UpO1xuICAgICAgICAgICAgaWYoZm9jdXNlZFZpZGVvSW5mbykge1xuICAgICAgICAgICAgICAgIHZhciBmb2N1c1Jlc291cmNlSmlkID0gZm9jdXNlZFZpZGVvSW5mby5yZXNvdXJjZUppZDtcbiAgICAgICAgICAgICAgICB2YXIgb2xkQ29udGFpbmVyID0gZ2V0UGFydGljaXBhbnRDb250YWluZXIoZm9jdXNSZXNvdXJjZUppZCk7XG5cbiAgICAgICAgICAgICAgICBpZiAob2xkQ29udGFpbmVyICYmIG9sZENvbnRhaW5lci5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgICAgIG9sZENvbnRhaW5lci5yZW1vdmVDbGFzcyhcInZpZGVvQ29udGFpbmVyRm9jdXNlZFwiKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZm9jdXNlZFZpZGVvSW5mbyA9IG51bGw7XG4gICAgICAgICAgICAgICAgaWYoZm9jdXNSZXNvdXJjZUppZCkge1xuICAgICAgICAgICAgICAgICAgICBBdmF0YXIuc2hvd1VzZXJBdmF0YXIoXG4gICAgICAgICAgICAgICAgICAgICAgICB4bXBwLmZpbmRKaWRGcm9tUmVzb3VyY2UoZm9jdXNSZXNvdXJjZUppZCkpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBJbmRpY2F0ZXMgaWYgdGhlIGxhcmdlIHZpZGVvIGlzIGN1cnJlbnRseSB2aXNpYmxlLlxuICAgICAqXG4gICAgICogQHJldHVybiA8dHQ+dHJ1ZTwvdHQ+IGlmIHZpc2libGUsIDx0dD5mYWxzZTwvdHQ+IC0gb3RoZXJ3aXNlXG4gICAgICovXG4gICAgbXkuaXNMYXJnZVZpZGVvVmlzaWJsZSA9IGZ1bmN0aW9uKCkge1xuICAgICAgICByZXR1cm4gJCgnI2xhcmdlVmlkZW8nKS5pcygnOnZpc2libGUnKTtcbiAgICB9O1xuXG4gICAgbXkuaXNMYXJnZVZpZGVvT25Ub3AgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBFdGhlcnBhZCA9IHJlcXVpcmUoXCIuLi9ldGhlcnBhZC9FdGhlcnBhZFwiKTtcbiAgICAgICAgdmFyIFByZXppID0gcmVxdWlyZShcIi4uL3ByZXppL1ByZXppXCIpO1xuICAgICAgICByZXR1cm4gIVByZXppLmlzUHJlc2VudGF0aW9uVmlzaWJsZSgpICYmICFFdGhlcnBhZC5pc1Zpc2libGUoKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogQ2hlY2tzIGlmIGNvbnRhaW5lciBmb3IgcGFydGljaXBhbnQgaWRlbnRpZmllZCBieSBnaXZlbiBwZWVySmlkIGV4aXN0c1xuICAgICAqIGluIHRoZSBkb2N1bWVudCBhbmQgY3JlYXRlcyBpdCBldmVudHVhbGx5LlxuICAgICAqIFxuICAgICAqIEBwYXJhbSBwZWVySmlkIHBlZXIgSmlkIHRvIGNoZWNrLlxuICAgICAqIEBwYXJhbSB1c2VySWQgdXNlciBlbWFpbCBvciBpZCBmb3Igc2V0dGluZyB0aGUgYXZhdGFyXG4gICAgICogXG4gICAgICogQHJldHVybiBSZXR1cm5zIDx0dD50cnVlPC90dD4gaWYgdGhlIHBlZXIgY29udGFpbmVyIGV4aXN0cyxcbiAgICAgKiA8dHQ+ZmFsc2U8L3R0PiAtIG90aGVyd2lzZVxuICAgICAqL1xuICAgIG15LmVuc3VyZVBlZXJDb250YWluZXJFeGlzdHMgPSBmdW5jdGlvbihwZWVySmlkLCB1c2VySWQpIHtcbiAgICAgICAgQ29udGFjdExpc3QuZW5zdXJlQWRkQ29udGFjdChwZWVySmlkLCB1c2VySWQpO1xuXG4gICAgICAgIHZhciByZXNvdXJjZUppZCA9IFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKHBlZXJKaWQpO1xuXG4gICAgICAgIHZhciB2aWRlb1NwYW5JZCA9ICdwYXJ0aWNpcGFudF8nICsgcmVzb3VyY2VKaWQ7XG5cbiAgICAgICAgaWYgKCEkKCcjJyArIHZpZGVvU3BhbklkKS5sZW5ndGgpIHtcbiAgICAgICAgICAgIHZhciBjb250YWluZXIgPVxuICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LmFkZFJlbW90ZVZpZGVvQ29udGFpbmVyKHBlZXJKaWQsIHZpZGVvU3BhbklkLCB1c2VySWQpO1xuICAgICAgICAgICAgQXZhdGFyLnNldFVzZXJBdmF0YXIocGVlckppZCwgdXNlcklkKTtcbiAgICAgICAgICAgIC8vIFNldCBkZWZhdWx0IGRpc3BsYXkgbmFtZS5cbiAgICAgICAgICAgIHNldERpc3BsYXlOYW1lKHZpZGVvU3BhbklkKTtcblxuICAgICAgICAgICAgVmlkZW9MYXlvdXQuY29ubmVjdGlvbkluZGljYXRvcnNbdmlkZW9TcGFuSWRdID1cbiAgICAgICAgICAgICAgICBuZXcgQ29ubmVjdGlvbkluZGljYXRvcihjb250YWluZXIsIHBlZXJKaWQsIFZpZGVvTGF5b3V0KTtcblxuICAgICAgICAgICAgdmFyIG5pY2tmaWVsZCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NwYW4nKTtcbiAgICAgICAgICAgIG5pY2tmaWVsZC5jbGFzc05hbWUgPSBcIm5pY2tcIjtcbiAgICAgICAgICAgIG5pY2tmaWVsZC5hcHBlbmRDaGlsZChkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShyZXNvdXJjZUppZCkpO1xuICAgICAgICAgICAgY29udGFpbmVyLmFwcGVuZENoaWxkKG5pY2tmaWVsZCk7XG5cbiAgICAgICAgICAgIC8vIEluIGNhc2UgdGhpcyBpcyBub3QgY3VycmVudGx5IGluIHRoZSBsYXN0IG4gd2UgZG9uJ3Qgc2hvdyBpdC5cbiAgICAgICAgICAgIGlmIChsb2NhbExhc3ROQ291bnRcbiAgICAgICAgICAgICAgICAmJiBsb2NhbExhc3ROQ291bnQgPiAwXG4gICAgICAgICAgICAgICAgJiYgJCgnI3JlbW90ZVZpZGVvcz5zcGFuJykubGVuZ3RoID49IGxvY2FsTGFzdE5Db3VudCArIDIpIHtcbiAgICAgICAgICAgICAgICBzaG93UGVlckNvbnRhaW5lcihyZXNvdXJjZUppZCwgJ2hpZGUnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2VcbiAgICAgICAgICAgICAgICBWaWRlb0xheW91dC5yZXNpemVUaHVtYm5haWxzKCk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgbXkuYWRkUmVtb3RlVmlkZW9Db250YWluZXIgPSBmdW5jdGlvbihwZWVySmlkLCBzcGFuSWQpIHtcbiAgICAgICAgdmFyIGNvbnRhaW5lciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NwYW4nKTtcbiAgICAgICAgY29udGFpbmVyLmlkID0gc3BhbklkO1xuICAgICAgICBjb250YWluZXIuY2xhc3NOYW1lID0gJ3ZpZGVvY29udGFpbmVyJztcbiAgICAgICAgdmFyIHJlbW90ZXMgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgncmVtb3RlVmlkZW9zJyk7XG5cbiAgICAgICAgLy8gSWYgdGhlIHBlZXJKaWQgaXMgbnVsbCB0aGVuIHRoaXMgdmlkZW8gc3BhbiBjb3VsZG4ndCBiZSBkaXJlY3RseVxuICAgICAgICAvLyBhc3NvY2lhdGVkIHdpdGggYSBwYXJ0aWNpcGFudCAodGhpcyBjb3VsZCBoYXBwZW4gaW4gdGhlIGNhc2Ugb2YgcHJlemkpLlxuICAgICAgICBpZiAoeG1wcC5pc01vZGVyYXRvcigpICYmIHBlZXJKaWQgIT09IG51bGwpXG4gICAgICAgICAgICBhZGRSZW1vdGVWaWRlb01lbnUocGVlckppZCwgY29udGFpbmVyKTtcblxuICAgICAgICByZW1vdGVzLmFwcGVuZENoaWxkKGNvbnRhaW5lcik7XG4gICAgICAgIEF1ZGlvTGV2ZWxzLnVwZGF0ZUF1ZGlvTGV2ZWxDYW52YXMocGVlckppZCwgVmlkZW9MYXlvdXQpO1xuXG4gICAgICAgIHJldHVybiBjb250YWluZXI7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYW4gYXVkaW8gb3IgdmlkZW8gc3RyZWFtIGVsZW1lbnQuXG4gICAgICovXG4gICAgbXkuY3JlYXRlU3RyZWFtRWxlbWVudCA9IGZ1bmN0aW9uIChzaWQsIHN0cmVhbSkge1xuICAgICAgICB2YXIgaXNWaWRlbyA9IHN0cmVhbS5nZXRWaWRlb1RyYWNrcygpLmxlbmd0aCA+IDA7XG5cbiAgICAgICAgdmFyIGVsZW1lbnQgPSBpc1ZpZGVvXG4gICAgICAgICAgICAgICAgICAgICAgICA/IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3ZpZGVvJylcbiAgICAgICAgICAgICAgICAgICAgICAgIDogZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnYXVkaW8nKTtcbiAgICAgICAgdmFyIGlkID0gKGlzVmlkZW8gPyAncmVtb3RlVmlkZW9fJyA6ICdyZW1vdGVBdWRpb18nKVxuICAgICAgICAgICAgICAgICAgICArIHNpZCArICdfJyArIFJUQy5nZXRTdHJlYW1JRChzdHJlYW0pO1xuXG4gICAgICAgIGVsZW1lbnQuaWQgPSBpZDtcbiAgICAgICAgZWxlbWVudC5hdXRvcGxheSA9IHRydWU7XG4gICAgICAgIGVsZW1lbnQub25jb250ZXh0bWVudSA9IGZ1bmN0aW9uICgpIHsgcmV0dXJuIGZhbHNlOyB9O1xuXG4gICAgICAgIHJldHVybiBlbGVtZW50O1xuICAgIH07XG5cbiAgICBteS5hZGRSZW1vdGVTdHJlYW1FbGVtZW50XG4gICAgICAgID0gZnVuY3Rpb24gKGNvbnRhaW5lciwgc2lkLCBzdHJlYW0sIHBlZXJKaWQsIHRoZXNzcmMpIHtcbiAgICAgICAgdmFyIG5ld0VsZW1lbnRJZCA9IG51bGw7XG5cbiAgICAgICAgdmFyIGlzVmlkZW8gPSBzdHJlYW0uZ2V0VmlkZW9UcmFja3MoKS5sZW5ndGggPiAwO1xuXG4gICAgICAgIGlmIChjb250YWluZXIpIHtcbiAgICAgICAgICAgIHZhciBzdHJlYW1FbGVtZW50ID0gVmlkZW9MYXlvdXQuY3JlYXRlU3RyZWFtRWxlbWVudChzaWQsIHN0cmVhbSk7XG4gICAgICAgICAgICBuZXdFbGVtZW50SWQgPSBzdHJlYW1FbGVtZW50LmlkO1xuXG4gICAgICAgICAgICBjb250YWluZXIuYXBwZW5kQ2hpbGQoc3RyZWFtRWxlbWVudCk7XG5cbiAgICAgICAgICAgIHZhciBzZWwgPSAkKCcjJyArIG5ld0VsZW1lbnRJZCk7XG4gICAgICAgICAgICBzZWwuaGlkZSgpO1xuXG4gICAgICAgICAgICAvLyBJZiB0aGUgY29udGFpbmVyIGlzIGN1cnJlbnRseSB2aXNpYmxlIHdlIGF0dGFjaCB0aGUgc3RyZWFtLlxuICAgICAgICAgICAgaWYgKCFpc1ZpZGVvXG4gICAgICAgICAgICAgICAgfHwgKGNvbnRhaW5lci5vZmZzZXRQYXJlbnQgIT09IG51bGwgJiYgaXNWaWRlbykpIHtcbiAgICAgICAgICAgICAgICB2YXIgdmlkZW9TdHJlYW0gPSBzaW11bGNhc3QuZ2V0UmVjZWl2aW5nVmlkZW9TdHJlYW0oc3RyZWFtKTtcbiAgICAgICAgICAgICAgICBSVEMuYXR0YWNoTWVkaWFTdHJlYW0oc2VsLCB2aWRlb1N0cmVhbSk7XG5cbiAgICAgICAgICAgICAgICBpZiAoaXNWaWRlbylcbiAgICAgICAgICAgICAgICAgICAgd2FpdEZvclJlbW90ZVZpZGVvKHNlbCwgdGhlc3NyYywgc3RyZWFtLCBwZWVySmlkKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgc3RyZWFtLm9uZW5kZWQgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS5sb2coJ3N0cmVhbSBlbmRlZCcsIHRoaXMpO1xuXG4gICAgICAgICAgICAgICAgVmlkZW9MYXlvdXQucmVtb3ZlUmVtb3RlU3RyZWFtRWxlbWVudChcbiAgICAgICAgICAgICAgICAgICAgc3RyZWFtLCBpc1ZpZGVvLCBjb250YWluZXIpO1xuXG4gICAgICAgICAgICAgICAgLy8gTk9URShncCkgaXQgc2VlbXMgdGhhdCB1bmRlciBjZXJ0YWluIGNpcmN1bXN0YW5jZXMsIHRoZVxuICAgICAgICAgICAgICAgIC8vIG9uZW5kZWQgZXZlbnQgaXMgbm90IGZpcmVkIGFuZCB0aHVzIHRoZSBjb250YWN0IGxpc3QgaXMgbm90XG4gICAgICAgICAgICAgICAgLy8gdXBkYXRlZC5cbiAgICAgICAgICAgICAgICAvL1xuICAgICAgICAgICAgICAgIC8vIFRoZSBvbmVuZGVkIGV2ZW50IG9mIGEgc3RyZWFtIHNob3VsZCBiZSBmaXJlZCB3aGVuIHRoZSBTU1JDc1xuICAgICAgICAgICAgICAgIC8vIGNvcnJlc3BvbmRpbmcgdG8gdGhhdCBzdHJlYW0gYXJlIHJlbW92ZWQgZnJvbSB0aGUgU0RQOyBidXRcbiAgICAgICAgICAgICAgICAvLyB0aGlzIGRvZXNuJ3Qgc2VlbSB0byBhbHdheXMgYmUgdGhlIGNhc2UsIHJlc3VsdGluZyBpbiBnaG9zdFxuICAgICAgICAgICAgICAgIC8vIGNvbnRhY3RzLlxuICAgICAgICAgICAgICAgIC8vXG4gICAgICAgICAgICAgICAgLy8gSW4gYW4gYXR0ZW1wdCB0byBmaXggdGhlIGdob3N0IGNvbnRhY3RzIHByb2JsZW0sIEknbSBtb3ZpbmdcbiAgICAgICAgICAgICAgICAvLyB0aGUgcmVtb3ZlQ29udGFjdCgpIG1ldGhvZCBjYWxsIGluIGFwcC5qcywgaW5zaWRlIHRoZVxuICAgICAgICAgICAgICAgIC8vICdtdWMubGVmdCcgZXZlbnQgaGFuZGxlci5cblxuICAgICAgICAgICAgICAgIC8vaWYgKHBlZXJKaWQpXG4gICAgICAgICAgICAgICAgLy8gICAgQ29udGFjdExpc3QucmVtb3ZlQ29udGFjdChwZWVySmlkKTtcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIC8vIEFkZCBjbGljayBoYW5kbGVyLlxuICAgICAgICAgICAgY29udGFpbmVyLm9uY2xpY2sgPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgICAgICAgICAvKlxuICAgICAgICAgICAgICAgICAqIEZJWE1FIEl0IHR1cm5zIG91dCB0aGF0IHZpZGVvVGh1bWIgbWF5IG5vdCBleGlzdCAoaWYgdGhlcmUgaXNcbiAgICAgICAgICAgICAgICAgKiBubyBhY3R1YWwgdmlkZW8pLlxuICAgICAgICAgICAgICAgICAqL1xuICAgICAgICAgICAgICAgIHZhciB2aWRlb1RodW1iID0gJCgnIycgKyBjb250YWluZXIuaWQgKyAnPnZpZGVvJykuZ2V0KDApO1xuICAgICAgICAgICAgICAgIGlmICh2aWRlb1RodW1iKSB7XG4gICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LmhhbmRsZVZpZGVvVGh1bWJDbGlja2VkKFxuICAgICAgICAgICAgICAgICAgICAgICAgUlRDLmdldFZpZGVvU3JjKHZpZGVvVGh1bWIpLFxuICAgICAgICAgICAgICAgICAgICAgICAgZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgICAgICBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChwZWVySmlkKSk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAvLyBBZGQgaG92ZXIgaGFuZGxlclxuICAgICAgICAgICAgJChjb250YWluZXIpLmhvdmVyKFxuICAgICAgICAgICAgICAgIGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgICAgICAgICBWaWRlb0xheW91dC5zaG93RGlzcGxheU5hbWUoY29udGFpbmVyLmlkLCB0cnVlKTtcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgICAgICAgICB2YXIgdmlkZW9TcmMgPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICBpZiAoJCgnIycgKyBjb250YWluZXIuaWQgKyAnPnZpZGVvJylcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiAkKCcjJyArIGNvbnRhaW5lci5pZCArICc+dmlkZW8nKS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB2aWRlb1NyYyA9IFJUQy5nZXRWaWRlb1NyYygkKCcjJyArIGNvbnRhaW5lci5pZCArICc+dmlkZW8nKS5nZXQoMCkpO1xuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gSWYgdGhlIHZpZGVvIGhhcyBiZWVuIFwicGlubmVkXCIgYnkgdGhlIHVzZXIgd2Ugd2FudCB0b1xuICAgICAgICAgICAgICAgICAgICAvLyBrZWVwIHRoZSBkaXNwbGF5IG5hbWUgb24gcGxhY2UuXG4gICAgICAgICAgICAgICAgICAgIGlmICghVmlkZW9MYXlvdXQuaXNMYXJnZVZpZGVvVmlzaWJsZSgpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfHwgdmlkZW9TcmMgIT09IFJUQy5nZXRWaWRlb1NyYygkKCcjbGFyZ2VWaWRlbycpWzBdKSlcbiAgICAgICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LnNob3dEaXNwbGF5TmFtZShjb250YWluZXIuaWQsIGZhbHNlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICApO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG5ld0VsZW1lbnRJZDtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlcyB0aGUgcmVtb3RlIHN0cmVhbSBlbGVtZW50IGNvcnJlc3BvbmRpbmcgdG8gdGhlIGdpdmVuIHN0cmVhbSBhbmRcbiAgICAgKiBwYXJlbnQgY29udGFpbmVyLlxuICAgICAqIFxuICAgICAqIEBwYXJhbSBzdHJlYW0gdGhlIHN0cmVhbVxuICAgICAqIEBwYXJhbSBpc1ZpZGVvIDx0dD50cnVlPC90dD4gaWYgZ2l2ZW4gPHR0PnN0cmVhbTwvdHQ+IGlzIGEgdmlkZW8gb25lLlxuICAgICAqIEBwYXJhbSBjb250YWluZXJcbiAgICAgKi9cbiAgICBteS5yZW1vdmVSZW1vdGVTdHJlYW1FbGVtZW50ID0gZnVuY3Rpb24gKHN0cmVhbSwgaXNWaWRlbywgY29udGFpbmVyKSB7XG4gICAgICAgIGlmICghY29udGFpbmVyKVxuICAgICAgICAgICAgcmV0dXJuO1xuXG4gICAgICAgIHZhciBzZWxlY3QgPSBudWxsO1xuICAgICAgICB2YXIgcmVtb3ZlZFZpZGVvU3JjID0gbnVsbDtcbiAgICAgICAgaWYgKGlzVmlkZW8pIHtcbiAgICAgICAgICAgIHNlbGVjdCA9ICQoJyMnICsgY29udGFpbmVyLmlkICsgJz52aWRlbycpO1xuICAgICAgICAgICAgcmVtb3ZlZFZpZGVvU3JjID0gUlRDLmdldFZpZGVvU3JjKHNlbGVjdC5nZXQoMCkpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2VcbiAgICAgICAgICAgIHNlbGVjdCA9ICQoJyMnICsgY29udGFpbmVyLmlkICsgJz5hdWRpbycpO1xuXG5cbiAgICAgICAgLy8gTWFyayB2aWRlbyBhcyByZW1vdmVkIHRvIGNhbmNlbCB3YWl0aW5nIGxvb3AoaWYgdmlkZW8gaXMgcmVtb3ZlZFxuICAgICAgICAvLyBiZWZvcmUgaGFzIHN0YXJ0ZWQpXG4gICAgICAgIHNlbGVjdC5yZW1vdmVkID0gdHJ1ZTtcbiAgICAgICAgc2VsZWN0LnJlbW92ZSgpO1xuXG4gICAgICAgIHZhciBhdWRpb0NvdW50ID0gJCgnIycgKyBjb250YWluZXIuaWQgKyAnPmF1ZGlvJykubGVuZ3RoO1xuICAgICAgICB2YXIgdmlkZW9Db3VudCA9ICQoJyMnICsgY29udGFpbmVyLmlkICsgJz52aWRlbycpLmxlbmd0aDtcblxuICAgICAgICBpZiAoIWF1ZGlvQ291bnQgJiYgIXZpZGVvQ291bnQpIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKFwiUmVtb3ZlIHdob2xlIHVzZXJcIiwgY29udGFpbmVyLmlkKTtcbiAgICAgICAgICAgIGlmKFZpZGVvTGF5b3V0LmNvbm5lY3Rpb25JbmRpY2F0b3JzW2NvbnRhaW5lci5pZF0pXG4gICAgICAgICAgICAgICAgVmlkZW9MYXlvdXQuY29ubmVjdGlvbkluZGljYXRvcnNbY29udGFpbmVyLmlkXS5yZW1vdmUoKTtcbiAgICAgICAgICAgIC8vIFJlbW92ZSB3aG9sZSBjb250YWluZXJcbiAgICAgICAgICAgIGNvbnRhaW5lci5yZW1vdmUoKTtcblxuICAgICAgICAgICAgVXRpbC5wbGF5U291bmROb3RpZmljYXRpb24oJ3VzZXJMZWZ0Jyk7XG4gICAgICAgICAgICBWaWRlb0xheW91dC5yZXNpemVUaHVtYm5haWxzKCk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAocmVtb3ZlZFZpZGVvU3JjKVxuICAgICAgICAgICAgVmlkZW9MYXlvdXQudXBkYXRlUmVtb3ZlZFZpZGVvKHJlbW92ZWRWaWRlb1NyYyk7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIFNob3cvaGlkZSBwZWVyIGNvbnRhaW5lciBmb3IgdGhlIGdpdmVuIHJlc291cmNlSmlkLlxuICAgICAqL1xuICAgIGZ1bmN0aW9uIHNob3dQZWVyQ29udGFpbmVyKHJlc291cmNlSmlkLCBzdGF0ZSkge1xuICAgICAgICB2YXIgcGVlckNvbnRhaW5lciA9ICQoJyNwYXJ0aWNpcGFudF8nICsgcmVzb3VyY2VKaWQpO1xuXG4gICAgICAgIGlmICghcGVlckNvbnRhaW5lcilcbiAgICAgICAgICAgIHJldHVybjtcblxuICAgICAgICB2YXIgaXNIaWRlID0gc3RhdGUgPT09ICdoaWRlJztcbiAgICAgICAgdmFyIHJlc2l6ZVRodW1ibmFpbHMgPSBmYWxzZTtcblxuICAgICAgICBpZiAoIWlzSGlkZSkge1xuICAgICAgICAgICAgaWYgKCFwZWVyQ29udGFpbmVyLmlzKCc6dmlzaWJsZScpKSB7XG4gICAgICAgICAgICAgICAgcmVzaXplVGh1bWJuYWlscyA9IHRydWU7XG4gICAgICAgICAgICAgICAgcGVlckNvbnRhaW5lci5zaG93KCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzdGF0ZSA9PSAnc2hvdycpXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgLy8gcGVlckNvbnRhaW5lci5jc3MoJy13ZWJraXQtZmlsdGVyJywgJycpO1xuICAgICAgICAgICAgICAgIHZhciBqaWQgPSB4bXBwLmZpbmRKaWRGcm9tUmVzb3VyY2UocmVzb3VyY2VKaWQpO1xuICAgICAgICAgICAgICAgIEF2YXRhci5zaG93VXNlckF2YXRhcihqaWQsIGZhbHNlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgLy8gaWYgKHN0YXRlID09ICdhdmF0YXInKVxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIC8vIHBlZXJDb250YWluZXIuY3NzKCctd2Via2l0LWZpbHRlcicsICdncmF5c2NhbGUoMTAwJSknKTtcbiAgICAgICAgICAgICAgICB2YXIgamlkID0geG1wcC5maW5kSmlkRnJvbVJlc291cmNlKHJlc291cmNlSmlkKTtcbiAgICAgICAgICAgICAgICBBdmF0YXIuc2hvd1VzZXJBdmF0YXIoamlkLCB0cnVlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChwZWVyQ29udGFpbmVyLmlzKCc6dmlzaWJsZScpICYmIGlzSGlkZSlcbiAgICAgICAge1xuICAgICAgICAgICAgcmVzaXplVGh1bWJuYWlscyA9IHRydWU7XG4gICAgICAgICAgICBwZWVyQ29udGFpbmVyLmhpZGUoKTtcbiAgICAgICAgICAgIGlmKFZpZGVvTGF5b3V0LmNvbm5lY3Rpb25JbmRpY2F0b3JzWydwYXJ0aWNpcGFudF8nICsgcmVzb3VyY2VKaWRdKVxuICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LmNvbm5lY3Rpb25JbmRpY2F0b3JzWydwYXJ0aWNpcGFudF8nICsgcmVzb3VyY2VKaWRdLmhpZGUoKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChyZXNpemVUaHVtYm5haWxzKSB7XG4gICAgICAgICAgICBWaWRlb0xheW91dC5yZXNpemVUaHVtYm5haWxzKCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBXZSB3YW50IHRvIGJlIGFibGUgdG8gcGluIGEgcGFydGljaXBhbnQgZnJvbSB0aGUgY29udGFjdCBsaXN0LCBldmVuXG4gICAgICAgIC8vIGlmIGhlJ3Mgbm90IGluIHRoZSBsYXN0TiBzZXQhXG4gICAgICAgIC8vIENvbnRhY3RMaXN0LnNldENsaWNrYWJsZShyZXNvdXJjZUppZCwgIWlzSGlkZSk7XG5cbiAgICB9O1xuXG4gICAgbXkuaW5wdXREaXNwbGF5TmFtZUhhbmRsZXIgPSBmdW5jdGlvbiAobmFtZSkge1xuICAgICAgICBpZiAobmFtZSAmJiBuaWNrbmFtZSAhPT0gbmFtZSkge1xuICAgICAgICAgICAgbmlja25hbWUgPSBuYW1lO1xuICAgICAgICAgICAgd2luZG93LmxvY2FsU3RvcmFnZS5kaXNwbGF5bmFtZSA9IG5pY2tuYW1lO1xuICAgICAgICAgICAgeG1wcC5hZGRUb1ByZXNlbmNlKFwiZGlzcGxheU5hbWVcIiwgbmlja25hbWUpO1xuXG4gICAgICAgICAgICBDaGF0LnNldENoYXRDb252ZXJzYXRpb25Nb2RlKHRydWUpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCEkKCcjbG9jYWxEaXNwbGF5TmFtZScpLmlzKFwiOnZpc2libGVcIikpIHtcbiAgICAgICAgICAgIGlmIChuaWNrbmFtZSlcbiAgICAgICAgICAgICAgICAkKCcjbG9jYWxEaXNwbGF5TmFtZScpLnRleHQobmlja25hbWUgKyBcIiAobWUpXCIpO1xuICAgICAgICAgICAgZWxzZVxuICAgICAgICAgICAgICAgICQoJyNsb2NhbERpc3BsYXlOYW1lJylcbiAgICAgICAgICAgICAgICAgICAgLnRleHQoaW50ZXJmYWNlQ29uZmlnLkRFRkFVTFRfTE9DQUxfRElTUExBWV9OQU1FKTtcbiAgICAgICAgICAgICQoJyNsb2NhbERpc3BsYXlOYW1lJykuc2hvdygpO1xuICAgICAgICB9XG5cbiAgICAgICAgJCgnI2VkaXREaXNwbGF5TmFtZScpLmhpZGUoKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogU2hvd3MvaGlkZXMgdGhlIGRpc3BsYXkgbmFtZSBvbiB0aGUgcmVtb3RlIHZpZGVvLlxuICAgICAqIEBwYXJhbSB2aWRlb1NwYW5JZCB0aGUgaWRlbnRpZmllciBvZiB0aGUgdmlkZW8gc3BhbiBlbGVtZW50XG4gICAgICogQHBhcmFtIGlzU2hvdyBpbmRpY2F0ZXMgaWYgdGhlIGRpc3BsYXkgbmFtZSBzaG91bGQgYmUgc2hvd24gb3IgaGlkZGVuXG4gICAgICovXG4gICAgbXkuc2hvd0Rpc3BsYXlOYW1lID0gZnVuY3Rpb24odmlkZW9TcGFuSWQsIGlzU2hvdykge1xuICAgICAgICB2YXIgbmFtZVNwYW4gPSAkKCcjJyArIHZpZGVvU3BhbklkICsgJz5zcGFuLmRpc3BsYXluYW1lJykuZ2V0KDApO1xuICAgICAgICBpZiAoaXNTaG93KSB7XG4gICAgICAgICAgICBpZiAobmFtZVNwYW4gJiYgbmFtZVNwYW4uaW5uZXJIVE1MICYmIG5hbWVTcGFuLmlubmVySFRNTC5sZW5ndGgpIFxuICAgICAgICAgICAgICAgIG5hbWVTcGFuLnNldEF0dHJpYnV0ZShcInN0eWxlXCIsIFwiZGlzcGxheTppbmxpbmUtYmxvY2s7XCIpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgaWYgKG5hbWVTcGFuKVxuICAgICAgICAgICAgICAgIG5hbWVTcGFuLnNldEF0dHJpYnV0ZShcInN0eWxlXCIsIFwiZGlzcGxheTpub25lO1wiKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTaG93cyB0aGUgcHJlc2VuY2Ugc3RhdHVzIG1lc3NhZ2UgZm9yIHRoZSBnaXZlbiB2aWRlby5cbiAgICAgKi9cbiAgICBteS5zZXRQcmVzZW5jZVN0YXR1cyA9IGZ1bmN0aW9uICh2aWRlb1NwYW5JZCwgc3RhdHVzTXNnKSB7XG5cbiAgICAgICAgaWYgKCEkKCcjJyArIHZpZGVvU3BhbklkKS5sZW5ndGgpIHtcbiAgICAgICAgICAgIC8vIE5vIGNvbnRhaW5lclxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIHN0YXR1c1NwYW4gPSAkKCcjJyArIHZpZGVvU3BhbklkICsgJz5zcGFuLnN0YXR1cycpO1xuICAgICAgICBpZiAoIXN0YXR1c1NwYW4ubGVuZ3RoKSB7XG4gICAgICAgICAgICAvL0FkZCBzdGF0dXMgc3BhblxuICAgICAgICAgICAgc3RhdHVzU3BhbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NwYW4nKTtcbiAgICAgICAgICAgIHN0YXR1c1NwYW4uY2xhc3NOYW1lID0gJ3N0YXR1cyc7XG4gICAgICAgICAgICBzdGF0dXNTcGFuLmlkID0gdmlkZW9TcGFuSWQgKyAnX3N0YXR1cyc7XG4gICAgICAgICAgICAkKCcjJyArIHZpZGVvU3BhbklkKVswXS5hcHBlbmRDaGlsZChzdGF0dXNTcGFuKTtcblxuICAgICAgICAgICAgc3RhdHVzU3BhbiA9ICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnPnNwYW4uc3RhdHVzJyk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBEaXNwbGF5IHN0YXR1c1xuICAgICAgICBpZiAoc3RhdHVzTXNnICYmIHN0YXR1c01zZy5sZW5ndGgpIHtcbiAgICAgICAgICAgICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnX3N0YXR1cycpLnRleHQoc3RhdHVzTXNnKTtcbiAgICAgICAgICAgIHN0YXR1c1NwYW4uZ2V0KDApLnNldEF0dHJpYnV0ZShcInN0eWxlXCIsIFwiZGlzcGxheTppbmxpbmUtYmxvY2s7XCIpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgLy8gSGlkZVxuICAgICAgICAgICAgc3RhdHVzU3Bhbi5nZXQoMCkuc2V0QXR0cmlidXRlKFwic3R5bGVcIiwgXCJkaXNwbGF5Om5vbmU7XCIpO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIFNob3dzIGEgdmlzdWFsIGluZGljYXRvciBmb3IgdGhlIG1vZGVyYXRvciBvZiB0aGUgY29uZmVyZW5jZS5cbiAgICAgKi9cbiAgICBteS5zaG93TW9kZXJhdG9ySW5kaWNhdG9yID0gZnVuY3Rpb24gKCkge1xuXG4gICAgICAgIHZhciBpc01vZGVyYXRvciA9IHhtcHAuaXNNb2RlcmF0b3IoKTtcbiAgICAgICAgaWYgKGlzTW9kZXJhdG9yKSB7XG4gICAgICAgICAgICB2YXIgaW5kaWNhdG9yU3BhbiA9ICQoJyNsb2NhbFZpZGVvQ29udGFpbmVyIC5mb2N1c2luZGljYXRvcicpO1xuXG4gICAgICAgICAgICBpZiAoaW5kaWNhdG9yU3Bhbi5jaGlsZHJlbigpLmxlbmd0aCA9PT0gMClcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBjcmVhdGVNb2RlcmF0b3JJbmRpY2F0b3JFbGVtZW50KGluZGljYXRvclNwYW5bMF0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgdmFyIG1lbWJlcnMgPSB4bXBwLmdldE1lbWJlcnMoKTtcblxuICAgICAgICBPYmplY3Qua2V5cyhtZW1iZXJzKS5mb3JFYWNoKGZ1bmN0aW9uIChqaWQpIHtcblxuICAgICAgICAgICAgaWYgKFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCkgPT09ICdmb2N1cycpIHtcbiAgICAgICAgICAgICAgICAvLyBTa2lwIHNlcnZlciBzaWRlIGZvY3VzXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgcmVzb3VyY2VKaWQgPSBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChqaWQpO1xuICAgICAgICAgICAgdmFyIHZpZGVvU3BhbklkID0gJ3BhcnRpY2lwYW50XycgKyByZXNvdXJjZUppZDtcbiAgICAgICAgICAgIHZhciB2aWRlb0NvbnRhaW5lciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHZpZGVvU3BhbklkKTtcblxuICAgICAgICAgICAgaWYgKCF2aWRlb0NvbnRhaW5lcikge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJObyB2aWRlbyBjb250YWluZXIgZm9yIFwiICsgamlkKTtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHZhciBtZW1iZXIgPSBtZW1iZXJzW2ppZF07XG5cbiAgICAgICAgICAgIGlmIChtZW1iZXIucm9sZSA9PT0gJ21vZGVyYXRvcicpIHtcbiAgICAgICAgICAgICAgICAvLyBSZW1vdmUgbWVudSBpZiBwZWVyIGlzIG1vZGVyYXRvclxuICAgICAgICAgICAgICAgIHZhciBtZW51U3BhbiA9ICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnPnNwYW4ucmVtb3RldmlkZW9tZW51Jyk7XG4gICAgICAgICAgICAgICAgaWYgKG1lbnVTcGFuLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICByZW1vdmVSZW1vdGVWaWRlb01lbnUodmlkZW9TcGFuSWQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAvLyBTaG93IG1vZGVyYXRvciBpbmRpY2F0b3JcbiAgICAgICAgICAgICAgICB2YXIgaW5kaWNhdG9yU3BhblxuICAgICAgICAgICAgICAgICAgICA9ICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnIC5mb2N1c2luZGljYXRvcicpO1xuXG4gICAgICAgICAgICAgICAgaWYgKCFpbmRpY2F0b3JTcGFuIHx8IGluZGljYXRvclNwYW4ubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgICAgICAgIGluZGljYXRvclNwYW4gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzcGFuJyk7XG4gICAgICAgICAgICAgICAgICAgIGluZGljYXRvclNwYW4uY2xhc3NOYW1lID0gJ2ZvY3VzaW5kaWNhdG9yJztcblxuICAgICAgICAgICAgICAgICAgICB2aWRlb0NvbnRhaW5lci5hcHBlbmRDaGlsZChpbmRpY2F0b3JTcGFuKTtcblxuICAgICAgICAgICAgICAgICAgICBjcmVhdGVNb2RlcmF0b3JJbmRpY2F0b3JFbGVtZW50KGluZGljYXRvclNwYW4pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gZWxzZSBpZiAoaXNNb2RlcmF0b3IpIHtcbiAgICAgICAgICAgICAgICAvLyBXZSBhcmUgbW9kZXJhdG9yLCBidXQgdXNlciBpcyBub3QgLSBhZGQgbWVudVxuICAgICAgICAgICAgICAgIGlmICgkKCcjcmVtb3RlX3BvcHVwbWVudV8nICsgcmVzb3VyY2VKaWQpLmxlbmd0aCA8PSAwKSB7XG4gICAgICAgICAgICAgICAgICAgIGFkZFJlbW90ZVZpZGVvTWVudShcbiAgICAgICAgICAgICAgICAgICAgICAgIGppZCxcbiAgICAgICAgICAgICAgICAgICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdwYXJ0aWNpcGFudF8nICsgcmVzb3VyY2VKaWQpKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTaG93cyB2aWRlbyBtdXRlZCBpbmRpY2F0b3Igb3ZlciBzbWFsbCB2aWRlb3MuXG4gICAgICovXG4gICAgbXkuc2hvd1ZpZGVvSW5kaWNhdG9yID0gZnVuY3Rpb24odmlkZW9TcGFuSWQsIGlzTXV0ZWQpIHtcbiAgICAgICAgdmFyIHZpZGVvTXV0ZWRTcGFuID0gJCgnIycgKyB2aWRlb1NwYW5JZCArICc+c3Bhbi52aWRlb011dGVkJyk7XG5cbiAgICAgICAgaWYgKGlzTXV0ZWQgPT09ICdmYWxzZScpIHtcbiAgICAgICAgICAgIGlmICh2aWRlb011dGVkU3Bhbi5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgdmlkZW9NdXRlZFNwYW4ucmVtb3ZlKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBpZih2aWRlb011dGVkU3Bhbi5sZW5ndGggPT0gMCkge1xuICAgICAgICAgICAgICAgIHZpZGVvTXV0ZWRTcGFuID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3BhbicpO1xuICAgICAgICAgICAgICAgIHZpZGVvTXV0ZWRTcGFuLmNsYXNzTmFtZSA9ICd2aWRlb011dGVkJztcblxuICAgICAgICAgICAgICAgICQoJyMnICsgdmlkZW9TcGFuSWQpWzBdLmFwcGVuZENoaWxkKHZpZGVvTXV0ZWRTcGFuKTtcblxuICAgICAgICAgICAgICAgIHZhciBtdXRlZEluZGljYXRvciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2knKTtcbiAgICAgICAgICAgICAgICBtdXRlZEluZGljYXRvci5jbGFzc05hbWUgPSAnaWNvbi1jYW1lcmEtZGlzYWJsZWQnO1xuICAgICAgICAgICAgICAgIFV0aWwuc2V0VG9vbHRpcChtdXRlZEluZGljYXRvcixcbiAgICAgICAgICAgICAgICAgICAgXCJQYXJ0aWNpcGFudCBoYXM8YnIvPnN0b3BwZWQgdGhlIGNhbWVyYS5cIixcbiAgICAgICAgICAgICAgICAgICAgXCJ0b3BcIik7XG4gICAgICAgICAgICAgICAgdmlkZW9NdXRlZFNwYW4uYXBwZW5kQ2hpbGQobXV0ZWRJbmRpY2F0b3IpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBWaWRlb0xheW91dC51cGRhdGVNdXRlUG9zaXRpb24odmlkZW9TcGFuSWQpO1xuXG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgbXkudXBkYXRlTXV0ZVBvc2l0aW9uID0gZnVuY3Rpb24gKHZpZGVvU3BhbklkKSB7XG4gICAgICAgIHZhciBhdWRpb011dGVkU3BhbiA9ICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnPnNwYW4uYXVkaW9NdXRlZCcpO1xuICAgICAgICB2YXIgY29ubmVjdGlvbkluZGljYXRvciA9ICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnPmRpdi5jb25uZWN0aW9uaW5kaWNhdG9yJyk7XG4gICAgICAgIHZhciB2aWRlb011dGVkU3BhbiA9ICQoJyMnICsgdmlkZW9TcGFuSWQgKyAnPnNwYW4udmlkZW9NdXRlZCcpO1xuICAgICAgICBpZihjb25uZWN0aW9uSW5kaWNhdG9yLmxlbmd0aCA+IDBcbiAgICAgICAgICAgICYmIGNvbm5lY3Rpb25JbmRpY2F0b3JbMF0uc3R5bGUuZGlzcGxheSAhPSBcIm5vbmVcIikge1xuICAgICAgICAgICAgYXVkaW9NdXRlZFNwYW4uY3NzKHtyaWdodDogXCIyM3B4XCJ9KTtcbiAgICAgICAgICAgIHZpZGVvTXV0ZWRTcGFuLmNzcyh7cmlnaHQ6ICgoYXVkaW9NdXRlZFNwYW4ubGVuZ3RoID4gMD8gMjMgOiAwKSArIDMwKSArIFwicHhcIn0pO1xuICAgICAgICB9XG4gICAgICAgIGVsc2VcbiAgICAgICAge1xuICAgICAgICAgICAgYXVkaW9NdXRlZFNwYW4uY3NzKHtyaWdodDogXCIwcHhcIn0pO1xuICAgICAgICAgICAgdmlkZW9NdXRlZFNwYW4uY3NzKHtyaWdodDogKGF1ZGlvTXV0ZWRTcGFuLmxlbmd0aCA+IDA/IDMwIDogMCkgKyBcInB4XCJ9KTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBTaG93cyBhdWRpbyBtdXRlZCBpbmRpY2F0b3Igb3ZlciBzbWFsbCB2aWRlb3MuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IGlzTXV0ZWRcbiAgICAgKi9cbiAgICBteS5zaG93QXVkaW9JbmRpY2F0b3IgPSBmdW5jdGlvbih2aWRlb1NwYW5JZCwgaXNNdXRlZCkge1xuICAgICAgICB2YXIgYXVkaW9NdXRlZFNwYW4gPSAkKCcjJyArIHZpZGVvU3BhbklkICsgJz5zcGFuLmF1ZGlvTXV0ZWQnKTtcblxuICAgICAgICBpZiAoaXNNdXRlZCA9PT0gJ2ZhbHNlJykge1xuICAgICAgICAgICAgaWYgKGF1ZGlvTXV0ZWRTcGFuLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgICAgICBhdWRpb011dGVkU3Bhbi5wb3BvdmVyKCdoaWRlJyk7XG4gICAgICAgICAgICAgICAgYXVkaW9NdXRlZFNwYW4ucmVtb3ZlKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBpZihhdWRpb011dGVkU3Bhbi5sZW5ndGggPT0gMCApIHtcbiAgICAgICAgICAgICAgICBhdWRpb011dGVkU3BhbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ3NwYW4nKTtcbiAgICAgICAgICAgICAgICBhdWRpb011dGVkU3Bhbi5jbGFzc05hbWUgPSAnYXVkaW9NdXRlZCc7XG4gICAgICAgICAgICAgICAgVXRpbC5zZXRUb29sdGlwKGF1ZGlvTXV0ZWRTcGFuLFxuICAgICAgICAgICAgICAgICAgICBcIlBhcnRpY2lwYW50IGlzIG11dGVkXCIsXG4gICAgICAgICAgICAgICAgICAgIFwidG9wXCIpO1xuXG4gICAgICAgICAgICAgICAgJCgnIycgKyB2aWRlb1NwYW5JZClbMF0uYXBwZW5kQ2hpbGQoYXVkaW9NdXRlZFNwYW4pO1xuICAgICAgICAgICAgICAgIHZhciBtdXRlZEluZGljYXRvciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2knKTtcbiAgICAgICAgICAgICAgICBtdXRlZEluZGljYXRvci5jbGFzc05hbWUgPSAnaWNvbi1taWMtZGlzYWJsZWQnO1xuICAgICAgICAgICAgICAgIGF1ZGlvTXV0ZWRTcGFuLmFwcGVuZENoaWxkKG11dGVkSW5kaWNhdG9yKTtcblxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgVmlkZW9MYXlvdXQudXBkYXRlTXV0ZVBvc2l0aW9uKHZpZGVvU3BhbklkKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKlxuICAgICAqIFNob3dzIG9yIGhpZGVzIHRoZSBhdWRpbyBtdXRlZCBpbmRpY2F0b3Igb3ZlciB0aGUgbG9jYWwgdGh1bWJuYWlsIHZpZGVvLlxuICAgICAqIEBwYXJhbSB7Ym9vbGVhbn0gaXNNdXRlZFxuICAgICAqL1xuICAgIG15LnNob3dMb2NhbEF1ZGlvSW5kaWNhdG9yID0gZnVuY3Rpb24oaXNNdXRlZCkge1xuICAgICAgICBWaWRlb0xheW91dC5zaG93QXVkaW9JbmRpY2F0b3IoJ2xvY2FsVmlkZW9Db250YWluZXInLCBpc011dGVkLnRvU3RyaW5nKCkpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBSZXNpemVzIHRoZSBsYXJnZSB2aWRlbyBjb250YWluZXIuXG4gICAgICovXG4gICAgbXkucmVzaXplTGFyZ2VWaWRlb0NvbnRhaW5lciA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgQ2hhdC5yZXNpemVDaGF0KCk7XG4gICAgICAgIHZhciBhdmFpbGFibGVIZWlnaHQgPSB3aW5kb3cuaW5uZXJIZWlnaHQ7XG4gICAgICAgIHZhciBhdmFpbGFibGVXaWR0aCA9IFVJVXRpbC5nZXRBdmFpbGFibGVWaWRlb1dpZHRoKCk7XG5cbiAgICAgICAgaWYgKGF2YWlsYWJsZVdpZHRoIDwgMCB8fCBhdmFpbGFibGVIZWlnaHQgPCAwKSByZXR1cm47XG5cbiAgICAgICAgJCgnI3ZpZGVvc3BhY2UnKS53aWR0aChhdmFpbGFibGVXaWR0aCk7XG4gICAgICAgICQoJyN2aWRlb3NwYWNlJykuaGVpZ2h0KGF2YWlsYWJsZUhlaWdodCk7XG4gICAgICAgICQoJyNsYXJnZVZpZGVvQ29udGFpbmVyJykud2lkdGgoYXZhaWxhYmxlV2lkdGgpO1xuICAgICAgICAkKCcjbGFyZ2VWaWRlb0NvbnRhaW5lcicpLmhlaWdodChhdmFpbGFibGVIZWlnaHQpO1xuXG4gICAgICAgIHZhciBhdmF0YXJTaXplID0gaW50ZXJmYWNlQ29uZmlnLkFDVElWRV9TUEVBS0VSX0FWQVRBUl9TSVpFO1xuICAgICAgICB2YXIgdG9wID0gYXZhaWxhYmxlSGVpZ2h0IC8gMiAtIGF2YXRhclNpemUgLyA0ICogMztcbiAgICAgICAgJCgnI2FjdGl2ZVNwZWFrZXInKS5jc3MoJ3RvcCcsIHRvcCk7XG5cbiAgICAgICAgVmlkZW9MYXlvdXQucmVzaXplVGh1bWJuYWlscygpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBSZXNpemVzIHRodW1ibmFpbHMuXG4gICAgICovXG4gICAgbXkucmVzaXplVGh1bWJuYWlscyA9IGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgdmlkZW9TcGFjZVdpZHRoID0gJCgnI3JlbW90ZVZpZGVvcycpLndpZHRoKCk7XG5cbiAgICAgICAgdmFyIHRodW1ibmFpbFNpemUgPSBWaWRlb0xheW91dC5jYWxjdWxhdGVUaHVtYm5haWxTaXplKHZpZGVvU3BhY2VXaWR0aCk7XG4gICAgICAgIHZhciB3aWR0aCA9IHRodW1ibmFpbFNpemVbMF07XG4gICAgICAgIHZhciBoZWlnaHQgPSB0aHVtYm5haWxTaXplWzFdO1xuXG4gICAgICAgIC8vIHNpemUgdmlkZW9zIHNvIHRoYXQgd2hpbGUga2VlcGluZyBBUiBhbmQgbWF4IGhlaWdodCwgd2UgaGF2ZSBhXG4gICAgICAgIC8vIG5pY2UgZml0XG4gICAgICAgICQoJyNyZW1vdGVWaWRlb3MnKS5oZWlnaHQoaGVpZ2h0KTtcbiAgICAgICAgJCgnI3JlbW90ZVZpZGVvcz5zcGFuJykud2lkdGgod2lkdGgpO1xuICAgICAgICAkKCcjcmVtb3RlVmlkZW9zPnNwYW4nKS5oZWlnaHQoaGVpZ2h0KTtcblxuICAgICAgICAkKCcudXNlckF2YXRhcicpLmNzcygnbGVmdCcsICh3aWR0aCAtIGhlaWdodCkgLyAyKTtcblxuICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKFwicmVtb3RldmlkZW8ucmVzaXplZFwiLCBbd2lkdGgsIGhlaWdodF0pO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBFbmFibGVzIHRoZSBkb21pbmFudCBzcGVha2VyIFVJLlxuICAgICAqXG4gICAgICogQHBhcmFtIHJlc291cmNlSmlkIHRoZSBqaWQgaW5kaWNhdGluZyB0aGUgdmlkZW8gZWxlbWVudCB0b1xuICAgICAqIGFjdGl2YXRlL2RlYWN0aXZhdGVcbiAgICAgKiBAcGFyYW0gaXNFbmFibGUgaW5kaWNhdGVzIGlmIHRoZSBkb21pbmFudCBzcGVha2VyIHNob3VsZCBiZSBlbmFibGVkIG9yXG4gICAgICogZGlzYWJsZWRcbiAgICAgKi9cbiAgICBteS5lbmFibGVEb21pbmFudFNwZWFrZXIgPSBmdW5jdGlvbihyZXNvdXJjZUppZCwgaXNFbmFibGUpIHtcblxuICAgICAgICB2YXIgdmlkZW9TcGFuSWQgPSBudWxsO1xuICAgICAgICB2YXIgdmlkZW9Db250YWluZXJJZCA9IG51bGw7XG4gICAgICAgIGlmIChyZXNvdXJjZUppZFxuICAgICAgICAgICAgICAgID09PSB4bXBwLm15UmVzb3VyY2UoKSkge1xuICAgICAgICAgICAgdmlkZW9TcGFuSWQgPSAnbG9jYWxWaWRlb1dyYXBwZXInO1xuICAgICAgICAgICAgdmlkZW9Db250YWluZXJJZCA9ICdsb2NhbFZpZGVvQ29udGFpbmVyJztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHZpZGVvU3BhbklkID0gJ3BhcnRpY2lwYW50XycgKyByZXNvdXJjZUppZDtcbiAgICAgICAgICAgIHZpZGVvQ29udGFpbmVySWQgPSB2aWRlb1NwYW5JZDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBkaXNwbGF5TmFtZSA9IHJlc291cmNlSmlkO1xuICAgICAgICB2YXIgbmFtZVNwYW4gPSAkKCcjJyArIHZpZGVvQ29udGFpbmVySWQgKyAnPnNwYW4uZGlzcGxheW5hbWUnKTtcbiAgICAgICAgaWYgKG5hbWVTcGFuLmxlbmd0aCA+IDApXG4gICAgICAgICAgICBkaXNwbGF5TmFtZSA9IG5hbWVTcGFuLmh0bWwoKTtcblxuICAgICAgICBjb25zb2xlLmxvZyhcIlVJIGVuYWJsZSBkb21pbmFudCBzcGVha2VyXCIsXG4gICAgICAgICAgICBkaXNwbGF5TmFtZSxcbiAgICAgICAgICAgIHJlc291cmNlSmlkLFxuICAgICAgICAgICAgaXNFbmFibGUpO1xuXG4gICAgICAgIHZpZGVvU3BhbiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHZpZGVvQ29udGFpbmVySWQpO1xuXG4gICAgICAgIGlmICghdmlkZW9TcGFuKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdmlkZW8gPSAkKCcjJyArIHZpZGVvU3BhbklkICsgJz52aWRlbycpO1xuXG4gICAgICAgIGlmICh2aWRlbyAmJiB2aWRlby5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBpZiAoaXNFbmFibGUpIHtcbiAgICAgICAgICAgICAgICB2YXIgaXNMYXJnZVZpZGVvVmlzaWJsZSA9IFZpZGVvTGF5b3V0LmlzTGFyZ2VWaWRlb09uVG9wKCk7XG4gICAgICAgICAgICAgICAgVmlkZW9MYXlvdXQuc2hvd0Rpc3BsYXlOYW1lKHZpZGVvQ29udGFpbmVySWQsIGlzTGFyZ2VWaWRlb1Zpc2libGUpO1xuXG4gICAgICAgICAgICAgICAgaWYgKCF2aWRlb1NwYW4uY2xhc3NMaXN0LmNvbnRhaW5zKFwiZG9taW5hbnRzcGVha2VyXCIpKVxuICAgICAgICAgICAgICAgICAgICB2aWRlb1NwYW4uY2xhc3NMaXN0LmFkZChcImRvbWluYW50c3BlYWtlclwiKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LnNob3dEaXNwbGF5TmFtZSh2aWRlb0NvbnRhaW5lcklkLCBmYWxzZSk7XG5cbiAgICAgICAgICAgICAgICBpZiAodmlkZW9TcGFuLmNsYXNzTGlzdC5jb250YWlucyhcImRvbWluYW50c3BlYWtlclwiKSlcbiAgICAgICAgICAgICAgICAgICAgdmlkZW9TcGFuLmNsYXNzTGlzdC5yZW1vdmUoXCJkb21pbmFudHNwZWFrZXJcIik7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIEF2YXRhci5zaG93VXNlckF2YXRhcihcbiAgICAgICAgICAgICAgICB4bXBwLmZpbmRKaWRGcm9tUmVzb3VyY2UocmVzb3VyY2VKaWQpKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBDYWxjdWxhdGVzIHRoZSB0aHVtYm5haWwgc2l6ZS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSB2aWRlb1NwYWNlV2lkdGggdGhlIHdpZHRoIG9mIHRoZSB2aWRlbyBzcGFjZVxuICAgICAqL1xuICAgIG15LmNhbGN1bGF0ZVRodW1ibmFpbFNpemUgPSBmdW5jdGlvbiAodmlkZW9TcGFjZVdpZHRoKSB7XG4gICAgICAgIC8vIENhbGN1bGF0ZSB0aGUgYXZhaWxhYmxlIGhlaWdodCwgd2hpY2ggaXMgdGhlIGlubmVyIHdpbmRvdyBoZWlnaHQgbWludXNcbiAgICAgICAvLyAzOXB4IGZvciB0aGUgaGVhZGVyIG1pbnVzIDJweCBmb3IgdGhlIGRlbGltaXRlciBsaW5lcyBvbiB0aGUgdG9wIGFuZFxuICAgICAgIC8vIGJvdHRvbSBvZiB0aGUgbGFyZ2UgdmlkZW8sIG1pbnVzIHRoZSAzNnB4IHNwYWNlIGluc2lkZSB0aGUgcmVtb3RlVmlkZW9zXG4gICAgICAgLy8gY29udGFpbmVyIHVzZWQgZm9yIGhpZ2hsaWdodGluZyBzaGFkb3cuXG4gICAgICAgdmFyIGF2YWlsYWJsZUhlaWdodCA9IDEwMDtcblxuICAgICAgICB2YXIgbnVtdmlkcyA9ICQoJyNyZW1vdGVWaWRlb3M+c3Bhbjp2aXNpYmxlJykubGVuZ3RoO1xuICAgICAgICBpZiAobG9jYWxMYXN0TkNvdW50ICYmIGxvY2FsTGFzdE5Db3VudCA+IDApIHtcbiAgICAgICAgICAgIG51bXZpZHMgPSBNYXRoLm1pbihsb2NhbExhc3ROQ291bnQgKyAxLCBudW12aWRzKTtcbiAgICAgICAgfVxuXG4gICAgICAgLy8gUmVtb3ZlIHRoZSAzcHggYm9yZGVycyBhcnJvdW5kIHZpZGVvcyBhbmQgYm9yZGVyIGFyb3VuZCB0aGUgcmVtb3RlXG4gICAgICAgLy8gdmlkZW9zIGFyZWEgYW5kIHRoZSA0IHBpeGVscyBiZXR3ZWVuIHRoZSBsb2NhbCB2aWRlbyBhbmQgdGhlIG90aGVyc1xuICAgICAgIC8vVE9ETzogRmluZCBvdXQgd2hlcmUgdGhlIDQgcGl4ZWxzIGNvbWUgZnJvbSBhbmQgcmVtb3ZlIHRoZW1cbiAgICAgICB2YXIgYXZhaWxhYmxlV2luV2lkdGggPSB2aWRlb1NwYWNlV2lkdGggLSAyICogMyAqIG51bXZpZHMgLSA3MCAtIDQ7XG5cbiAgICAgICB2YXIgYXZhaWxhYmxlV2lkdGggPSBhdmFpbGFibGVXaW5XaWR0aCAvIG51bXZpZHM7XG4gICAgICAgdmFyIGFzcGVjdFJhdGlvID0gMTYuMCAvIDkuMDtcbiAgICAgICB2YXIgbWF4SGVpZ2h0ID0gTWF0aC5taW4oMTYwLCBhdmFpbGFibGVIZWlnaHQpO1xuICAgICAgIGF2YWlsYWJsZUhlaWdodCA9IE1hdGgubWluKG1heEhlaWdodCwgYXZhaWxhYmxlV2lkdGggLyBhc3BlY3RSYXRpbyk7XG4gICAgICAgaWYgKGF2YWlsYWJsZUhlaWdodCA8IGF2YWlsYWJsZVdpZHRoIC8gYXNwZWN0UmF0aW8pIHtcbiAgICAgICAgICAgYXZhaWxhYmxlV2lkdGggPSBNYXRoLmZsb29yKGF2YWlsYWJsZUhlaWdodCAqIGFzcGVjdFJhdGlvKTtcbiAgICAgICB9XG5cbiAgICAgICByZXR1cm4gW2F2YWlsYWJsZVdpZHRoLCBhdmFpbGFibGVIZWlnaHRdO1xuICAgfTtcblxuICAgIC8qKlxuICAgICAqIFVwZGF0ZXMgdGhlIHJlbW90ZSB2aWRlbyBtZW51LlxuICAgICAqXG4gICAgICogQHBhcmFtIGppZCB0aGUgamlkIGluZGljYXRpbmcgdGhlIHZpZGVvIGZvciB3aGljaCB3ZSdyZSBhZGRpbmcgYSBtZW51LlxuICAgICAqIEBwYXJhbSBpc011dGVkIGluZGljYXRlcyB0aGUgY3VycmVudCBtdXRlIHN0YXRlXG4gICAgICovXG4gICAgbXkudXBkYXRlUmVtb3RlVmlkZW9NZW51ID0gZnVuY3Rpb24oamlkLCBpc011dGVkKSB7XG4gICAgICAgIHZhciBtdXRlTWVudUl0ZW1cbiAgICAgICAgICAgID0gJCgnI3JlbW90ZV9wb3B1cG1lbnVfJ1xuICAgICAgICAgICAgICAgICAgICArIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZClcbiAgICAgICAgICAgICAgICAgICAgKyAnPmxpPmEubXV0ZWxpbmsnKTtcblxuICAgICAgICB2YXIgbXV0ZWRJbmRpY2F0b3IgPSBcIjxpIGNsYXNzPSdpY29uLW1pYy1kaXNhYmxlZCc+PC9pPlwiO1xuXG4gICAgICAgIGlmIChtdXRlTWVudUl0ZW0ubGVuZ3RoKSB7XG4gICAgICAgICAgICB2YXIgbXV0ZUxpbmsgPSBtdXRlTWVudUl0ZW0uZ2V0KDApO1xuXG4gICAgICAgICAgICBpZiAoaXNNdXRlZCA9PT0gJ3RydWUnKSB7XG4gICAgICAgICAgICAgICAgbXV0ZUxpbmsuaW5uZXJIVE1MID0gbXV0ZWRJbmRpY2F0b3IgKyAnIE11dGVkJztcbiAgICAgICAgICAgICAgICBtdXRlTGluay5jbGFzc05hbWUgPSAnbXV0ZWxpbmsgZGlzYWJsZWQnO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgbXV0ZUxpbmsuaW5uZXJIVE1MID0gbXV0ZWRJbmRpY2F0b3IgKyAnIE11dGUnO1xuICAgICAgICAgICAgICAgIG11dGVMaW5rLmNsYXNzTmFtZSA9ICdtdXRlbGluayc7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgY3VycmVudCBkb21pbmFudCBzcGVha2VyIHJlc291cmNlIGppZC5cbiAgICAgKi9cbiAgICBteS5nZXREb21pbmFudFNwZWFrZXJSZXNvdXJjZUppZCA9IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIGN1cnJlbnREb21pbmFudFNwZWFrZXI7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGNvcnJlc3BvbmRpbmcgcmVzb3VyY2UgamlkIHRvIHRoZSBnaXZlbiBwZWVyIGNvbnRhaW5lclxuICAgICAqIERPTSBlbGVtZW50LlxuICAgICAqXG4gICAgICogQHJldHVybiB0aGUgY29ycmVzcG9uZGluZyByZXNvdXJjZSBqaWQgdG8gdGhlIGdpdmVuIHBlZXIgY29udGFpbmVyXG4gICAgICogRE9NIGVsZW1lbnRcbiAgICAgKi9cbiAgICBteS5nZXRQZWVyQ29udGFpbmVyUmVzb3VyY2VKaWQgPSBmdW5jdGlvbiAoY29udGFpbmVyRWxlbWVudCkge1xuICAgICAgICB2YXIgaSA9IGNvbnRhaW5lckVsZW1lbnQuaWQuaW5kZXhPZigncGFydGljaXBhbnRfJyk7XG5cbiAgICAgICAgaWYgKGkgPj0gMClcbiAgICAgICAgICAgIHJldHVybiBjb250YWluZXJFbGVtZW50LmlkLnN1YnN0cmluZyhpICsgMTIpOyBcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogT24gY29udGFjdCBsaXN0IGl0ZW0gY2xpY2tlZC5cbiAgICAgKi9cbiAgICAkKENvbnRhY3RMaXN0KS5iaW5kKCdjb250YWN0Y2xpY2tlZCcsIGZ1bmN0aW9uKGV2ZW50LCBqaWQpIHtcbiAgICAgICAgaWYgKCFqaWQpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciByZXNvdXJjZSA9IFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCk7XG4gICAgICAgIHZhciB2aWRlb0NvbnRhaW5lciA9ICQoXCIjcGFydGljaXBhbnRfXCIgKyByZXNvdXJjZSk7XG4gICAgICAgIGlmICh2aWRlb0NvbnRhaW5lci5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICB2YXIgdmlkZW9UaHVtYiA9ICQoJ3ZpZGVvJywgdmlkZW9Db250YWluZXIpLmdldCgwKTtcbiAgICAgICAgICAgIC8vIEl0IGlzIG5vdCBhbHdheXMgdGhlIGNhc2UgdGhhdCBhIHZpZGVvVGh1bWIgZXhpc3RzIChpZiB0aGVyZSBpc1xuICAgICAgICAgICAgLy8gbm8gYWN0dWFsIHZpZGVvKS5cbiAgICAgICAgICAgIGlmICh2aWRlb1RodW1iKSB7XG4gICAgICAgICAgICAgICAgaWYgKHZpZGVvVGh1bWIuc3JjICYmIHZpZGVvVGh1bWIuc3JjICE9ICcnKSB7XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gV2UgaGF2ZSBhIHZpZGVvIHNyYywgZ3JlYXQhIExldCdzIHVwZGF0ZSB0aGUgbGFyZ2UgdmlkZW9cbiAgICAgICAgICAgICAgICAgICAgLy8gbm93LlxuXG4gICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LmhhbmRsZVZpZGVvVGh1bWJDbGlja2VkKFxuICAgICAgICAgICAgICAgICAgICAgICAgdmlkZW9UaHVtYi5zcmMsXG4gICAgICAgICAgICAgICAgICAgICAgICBmYWxzZSxcbiAgICAgICAgICAgICAgICAgICAgICAgIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCkpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gSWYgd2UgZG9uJ3QgaGF2ZSBhIHZpZGVvIHNyYyBmb3IgamlkLCB0aGVyZSdzIGFic29sdXRlbHlcbiAgICAgICAgICAgICAgICAgICAgLy8gbm8gcG9pbnQgaW4gY2FsbGluZyBoYW5kbGVWaWRlb1RodW1iQ2xpY2tlZDsgUXVpdGVcbiAgICAgICAgICAgICAgICAgICAgLy8gc2ltcGx5LCBpdCB3b24ndCB3b3JrIGJlY2F1c2UgaXQgbmVlZHMgYW4gc3JjIHRvIGF0dGFjaFxuICAgICAgICAgICAgICAgICAgICAvLyB0byB0aGUgbGFyZ2UgdmlkZW8uXG4gICAgICAgICAgICAgICAgICAgIC8vXG4gICAgICAgICAgICAgICAgICAgIC8vIEluc3RlYWQsIHdlIHRyaWdnZXIgdGhlIHBpbm5lZCBlbmRwb2ludCBjaGFuZ2VkIGV2ZW50IHRvXG4gICAgICAgICAgICAgICAgICAgIC8vIGxldCB0aGUgYnJpZGdlIGFkanVzdCBpdHMgbGFzdE4gc2V0IGZvciBteWppZCBhbmQgc3RvcmVcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhlIHBpbm5lZCB1c2VyIGluIHRoZSBsYXN0TlBpY2t1cEppZCB2YXJpYWJsZSB0byBiZVxuICAgICAgICAgICAgICAgICAgICAvLyBwaWNrZWQgdXAgbGF0ZXIgYnkgdGhlIGxhc3ROIGNoYW5nZWQgZXZlbnQgaGFuZGxlci5cblxuICAgICAgICAgICAgICAgICAgICBsYXN0TlBpY2t1cEppZCA9IGppZDtcbiAgICAgICAgICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcihcInBpbm5lZGVuZHBvaW50Y2hhbmdlZFwiLCBbamlkXSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIGlmIChqaWQgPT0geG1wcC5teUppZCgpKSB7XG4gICAgICAgICAgICAgICAgJChcIiNsb2NhbFZpZGVvQ29udGFpbmVyXCIpLmNsaWNrKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9KTtcblxuICAgIC8qKlxuICAgICAqIE9uIGF1ZGlvIG11dGVkIGV2ZW50LlxuICAgICAqL1xuICAgICQoZG9jdW1lbnQpLmJpbmQoJ2F1ZGlvbXV0ZWQubXVjJywgZnVuY3Rpb24gKGV2ZW50LCBqaWQsIGlzTXV0ZWQpIHtcbiAgICAgICAgLypcbiAgICAgICAgIC8vIEZJWE1FOiBidXQgZm9jdXMgY2FuIG5vdCBtdXRlIGluIHRoaXMgY2FzZSA/IC0gY2hlY2tcbiAgICAgICAgaWYgKGppZCA9PT0geG1wcC5teUppZCgpKSB7XG5cbiAgICAgICAgICAgIC8vIFRoZSBsb2NhbCBtdXRlIGluZGljYXRvciBpcyBjb250cm9sbGVkIGxvY2FsbHlcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfSovXG4gICAgICAgIHZhciB2aWRlb1NwYW5JZCA9IG51bGw7XG4gICAgICAgIGlmIChqaWQgPT09IHhtcHAubXlKaWQoKSkge1xuICAgICAgICAgICAgdmlkZW9TcGFuSWQgPSAnbG9jYWxWaWRlb0NvbnRhaW5lcic7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBWaWRlb0xheW91dC5lbnN1cmVQZWVyQ29udGFpbmVyRXhpc3RzKGppZCk7XG4gICAgICAgICAgICB2aWRlb1NwYW5JZCA9ICdwYXJ0aWNpcGFudF8nICsgU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKTtcbiAgICAgICAgfVxuXG4gICAgICAgIG11dGVkQXVkaW9zW2ppZF0gPSBpc011dGVkO1xuXG4gICAgICAgIGlmICh4bXBwLmlzTW9kZXJhdG9yKCkpIHtcbiAgICAgICAgICAgIFZpZGVvTGF5b3V0LnVwZGF0ZVJlbW90ZVZpZGVvTWVudShqaWQsIGlzTXV0ZWQpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHZpZGVvU3BhbklkKVxuICAgICAgICAgICAgVmlkZW9MYXlvdXQuc2hvd0F1ZGlvSW5kaWNhdG9yKHZpZGVvU3BhbklkLCBpc011dGVkKTtcbiAgICB9KTtcblxuICAgIC8qKlxuICAgICAqIE9uIHZpZGVvIG11dGVkIGV2ZW50LlxuICAgICAqL1xuICAgICQoZG9jdW1lbnQpLmJpbmQoJ3ZpZGVvbXV0ZWQubXVjJywgZnVuY3Rpb24gKGV2ZW50LCBqaWQsIHZhbHVlKSB7XG4gICAgICAgIHZhciBpc011dGVkID0gKHZhbHVlID09PSBcInRydWVcIik7XG4gICAgICAgIGlmKCFSVEMubXV0ZVJlbW90ZVZpZGVvU3RyZWFtKGppZCwgaXNNdXRlZCkpXG4gICAgICAgICAgICByZXR1cm47XG5cbiAgICAgICAgQXZhdGFyLnNob3dVc2VyQXZhdGFyKGppZCwgaXNNdXRlZCk7XG4gICAgICAgIHZhciB2aWRlb1NwYW5JZCA9IG51bGw7XG4gICAgICAgIGlmIChqaWQgPT09IHhtcHAubXlKaWQoKSkge1xuICAgICAgICAgICAgdmlkZW9TcGFuSWQgPSAnbG9jYWxWaWRlb0NvbnRhaW5lcic7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBWaWRlb0xheW91dC5lbnN1cmVQZWVyQ29udGFpbmVyRXhpc3RzKGppZCk7XG4gICAgICAgICAgICB2aWRlb1NwYW5JZCA9ICdwYXJ0aWNpcGFudF8nICsgU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh2aWRlb1NwYW5JZClcbiAgICAgICAgICAgIFZpZGVvTGF5b3V0LnNob3dWaWRlb0luZGljYXRvcih2aWRlb1NwYW5JZCwgdmFsdWUpO1xuICAgIH0pO1xuXG4gICAgLyoqXG4gICAgICogRGlzcGxheSBuYW1lIGNoYW5nZWQuXG4gICAgICovXG4gICAgbXkub25EaXNwbGF5TmFtZUNoYW5nZWQgPVxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbiAoamlkLCBkaXNwbGF5TmFtZSwgc3RhdHVzKSB7XG4gICAgICAgIHZhciBuYW1lID0gbnVsbDtcbiAgICAgICAgaWYgKGppZCA9PT0gJ2xvY2FsVmlkZW9Db250YWluZXInXG4gICAgICAgICAgICB8fCBqaWQgPT09IHhtcHAubXlKaWQoKSkge1xuICAgICAgICAgICAgbmFtZSA9IG5pY2tuYW1lO1xuICAgICAgICAgICAgc2V0RGlzcGxheU5hbWUoJ2xvY2FsVmlkZW9Db250YWluZXInLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzcGxheU5hbWUpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgVmlkZW9MYXlvdXQuZW5zdXJlUGVlckNvbnRhaW5lckV4aXN0cyhqaWQpO1xuICAgICAgICAgICAgbmFtZSA9ICQoJyNwYXJ0aWNpcGFudF8nICsgU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKSArIFwiX25hbWVcIikudGV4dCgpO1xuICAgICAgICAgICAgc2V0RGlzcGxheU5hbWUoXG4gICAgICAgICAgICAgICAgJ3BhcnRpY2lwYW50XycgKyBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChqaWQpLFxuICAgICAgICAgICAgICAgIGRpc3BsYXlOYW1lLFxuICAgICAgICAgICAgICAgIHN0YXR1cyk7XG4gICAgICAgIH1cblxuICAgICAgICBpZihqaWQgPT09ICdsb2NhbFZpZGVvQ29udGFpbmVyJylcbiAgICAgICAgICAgIGppZCA9IHhtcHAubXlKaWQoKTtcbiAgICAgICAgaWYoIW5hbWUgfHwgbmFtZSAhPSBkaXNwbGF5TmFtZSlcbiAgICAgICAgICAgIEFQSS50cmlnZ2VyRXZlbnQoXCJkaXNwbGF5TmFtZUNoYW5nZVwiLHtqaWQ6IGppZCwgZGlzcGxheW5hbWU6IGRpc3BsYXlOYW1lfSk7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIE9uIGRvbWluYW50IHNwZWFrZXIgY2hhbmdlZCBldmVudC5cbiAgICAgKi9cbiAgICAkKGRvY3VtZW50KS5iaW5kKCdkb21pbmFudHNwZWFrZXJjaGFuZ2VkJywgZnVuY3Rpb24gKGV2ZW50LCByZXNvdXJjZUppZCkge1xuICAgICAgICAvLyBXZSBpZ25vcmUgbG9jYWwgdXNlciBldmVudHMuXG4gICAgICAgIGlmIChyZXNvdXJjZUppZFxuICAgICAgICAgICAgICAgID09PSB4bXBwLm15UmVzb3VyY2UoKSlcbiAgICAgICAgICAgIHJldHVybjtcblxuICAgICAgICAvLyBVcGRhdGUgdGhlIGN1cnJlbnQgZG9taW5hbnQgc3BlYWtlci5cbiAgICAgICAgaWYgKHJlc291cmNlSmlkICE9PSBjdXJyZW50RG9taW5hbnRTcGVha2VyKSB7XG4gICAgICAgICAgICB2YXIgb2xkU3BlYWtlclZpZGVvU3BhbklkID0gXCJwYXJ0aWNpcGFudF9cIiArIGN1cnJlbnREb21pbmFudFNwZWFrZXIsXG4gICAgICAgICAgICAgICAgbmV3U3BlYWtlclZpZGVvU3BhbklkID0gXCJwYXJ0aWNpcGFudF9cIiArIHJlc291cmNlSmlkO1xuICAgICAgICAgICAgaWYoJChcIiNcIiArIG9sZFNwZWFrZXJWaWRlb1NwYW5JZCArIFwiPnNwYW4uZGlzcGxheW5hbWVcIikudGV4dCgpID09PVxuICAgICAgICAgICAgICAgIGludGVyZmFjZUNvbmZpZy5ERUZBVUxUX0RPTUlOQU5UX1NQRUFLRVJfRElTUExBWV9OQU1FKSB7XG4gICAgICAgICAgICAgICAgc2V0RGlzcGxheU5hbWUob2xkU3BlYWtlclZpZGVvU3BhbklkLCBudWxsKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmKCQoXCIjXCIgKyBuZXdTcGVha2VyVmlkZW9TcGFuSWQgKyBcIj5zcGFuLmRpc3BsYXluYW1lXCIpLnRleHQoKSA9PT1cbiAgICAgICAgICAgICAgICBpbnRlcmZhY2VDb25maWcuREVGQVVMVF9SRU1PVEVfRElTUExBWV9OQU1FKSB7XG4gICAgICAgICAgICAgICAgc2V0RGlzcGxheU5hbWUobmV3U3BlYWtlclZpZGVvU3BhbklkLFxuICAgICAgICAgICAgICAgICAgICBpbnRlcmZhY2VDb25maWcuREVGQVVMVF9ET01JTkFOVF9TUEVBS0VSX0RJU1BMQVlfTkFNRSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjdXJyZW50RG9taW5hbnRTcGVha2VyID0gcmVzb3VyY2VKaWQ7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICAvLyBPYnRhaW4gY29udGFpbmVyIGZvciBuZXcgZG9taW5hbnQgc3BlYWtlci5cbiAgICAgICAgdmFyIGNvbnRhaW5lciAgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChcbiAgICAgICAgICAgICAgICAncGFydGljaXBhbnRfJyArIHJlc291cmNlSmlkKTtcblxuICAgICAgICAvLyBMb2NhbCB2aWRlbyB3aWxsIG5vdCBoYXZlIGNvbnRhaW5lciBmb3VuZCwgYnV0IHRoYXQncyBva1xuICAgICAgICAvLyBzaW5jZSB3ZSBkb24ndCB3YW50IHRvIHN3aXRjaCB0byBsb2NhbCB2aWRlby5cbiAgICAgICAgaWYgKGNvbnRhaW5lciAmJiAhZm9jdXNlZFZpZGVvSW5mbylcbiAgICAgICAge1xuICAgICAgICAgICAgdmFyIHZpZGVvID0gY29udGFpbmVyLmdldEVsZW1lbnRzQnlUYWdOYW1lKFwidmlkZW9cIik7XG5cbiAgICAgICAgICAgIC8vIFVwZGF0ZSB0aGUgbGFyZ2UgdmlkZW8gaWYgdGhlIHZpZGVvIHNvdXJjZSBpcyBhbHJlYWR5IGF2YWlsYWJsZSxcbiAgICAgICAgICAgIC8vIG90aGVyd2lzZSB3YWl0IGZvciB0aGUgXCJ2aWRlb2FjdGl2ZS5qaW5nbGVcIiBldmVudC5cbiAgICAgICAgICAgIGlmICh2aWRlby5sZW5ndGggJiYgdmlkZW9bMF0uY3VycmVudFRpbWUgPiAwKVxuICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LnVwZGF0ZUxhcmdlVmlkZW8oUlRDLmdldFZpZGVvU3JjKHZpZGVvWzBdKSwgcmVzb3VyY2VKaWQpO1xuICAgICAgICB9XG4gICAgfSk7XG5cbiAgICAvKipcbiAgICAgKiBPbiBsYXN0IE4gY2hhbmdlIGV2ZW50LlxuICAgICAqXG4gICAgICogQHBhcmFtIGV2ZW50IHRoZSBldmVudCB0aGF0IG5vdGlmaWVkIHVzXG4gICAgICogQHBhcmFtIGxhc3RORW5kcG9pbnRzIHRoZSBsaXN0IG9mIGxhc3QgTiBlbmRwb2ludHNcbiAgICAgKiBAcGFyYW0gZW5kcG9pbnRzRW50ZXJpbmdMYXN0TiB0aGUgbGlzdCBjdXJyZW50bHkgZW50ZXJpbmcgbGFzdCBOXG4gICAgICogZW5kcG9pbnRzXG4gICAgICovXG4gICAgJChkb2N1bWVudCkuYmluZCgnbGFzdG5jaGFuZ2VkJywgZnVuY3Rpb24gKCBldmVudCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RORW5kcG9pbnRzLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZW5kcG9pbnRzRW50ZXJpbmdMYXN0TixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmVhbSkge1xuICAgICAgICBpZiAobGFzdE5Db3VudCAhPT0gbGFzdE5FbmRwb2ludHMubGVuZ3RoKVxuICAgICAgICAgICAgbGFzdE5Db3VudCA9IGxhc3RORW5kcG9pbnRzLmxlbmd0aDtcblxuICAgICAgICBsYXN0TkVuZHBvaW50c0NhY2hlID0gbGFzdE5FbmRwb2ludHM7XG5cbiAgICAgICAgLy8gU2F5IEEsIEIsIEMsIEQsIEUsIGFuZCBGIGFyZSBpbiBhIGNvbmZlcmVuY2UgYW5kIExhc3ROID0gMy5cbiAgICAgICAgLy9cbiAgICAgICAgLy8gSWYgTGFzdE4gZHJvcHMgdG8sIHNheSwgMiwgYmVjYXVzZSBvZiBhZGFwdGl2aXR5LCB0aGVuIEUgc2hvdWxkIHNlZVxuICAgICAgICAvLyB0aHVtYm5haWxzIGZvciBBLCBCIGFuZCBDLiBBIGFuZCBCIGFyZSBpbiBFJ3Mgc2VydmVyIHNpZGUgTGFzdE4gc2V0LFxuICAgICAgICAvLyBzbyBFIHNlZXMgdGhlbS4gQyBpcyBvbmx5IGluIEUncyBsb2NhbCBMYXN0TiBzZXQuXG4gICAgICAgIC8vXG4gICAgICAgIC8vIElmIEYgc3RhcnRzIHRhbGtpbmcgYW5kIExhc3ROID0gMywgdGhlbiBFIHNob3VsZCBzZWUgdGh1bWJuYWlscyBmb3JcbiAgICAgICAgLy8gRiwgQSwgQi4gQiBnZXRzIFwiZWplY3RlZFwiIGZyb20gRSdzIHNlcnZlciBzaWRlIExhc3ROIHNldCwgYnV0IGl0XG4gICAgICAgIC8vIGVudGVycyBFJ3MgbG9jYWwgTGFzdE4gZWplY3RpbmcgQy5cblxuICAgICAgICAvLyBJbmNyZWFzZSB0aGUgbG9jYWwgTGFzdE4gc2V0IHNpemUsIGlmIG5lY2Vzc2FyeS5cbiAgICAgICAgaWYgKGxhc3ROQ291bnQgPiBsb2NhbExhc3ROQ291bnQpIHtcbiAgICAgICAgICAgIGxvY2FsTGFzdE5Db3VudCA9IGxhc3ROQ291bnQ7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBVcGRhdGUgdGhlIGxvY2FsIExhc3ROIHNldCBwcmVzZXJ2aW5nIHRoZSBvcmRlciBpbiB3aGljaCB0aGVcbiAgICAgICAgLy8gZW5kcG9pbnRzIGFwcGVhcmVkIGluIHRoZSBMYXN0Ti9sb2NhbCBMYXN0TiBzZXQuXG5cbiAgICAgICAgdmFyIG5leHRMb2NhbExhc3ROU2V0ID0gbGFzdE5FbmRwb2ludHMuc2xpY2UoMCk7XG4gICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbG9jYWxMYXN0TlNldC5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgaWYgKG5leHRMb2NhbExhc3ROU2V0Lmxlbmd0aCA+PSBsb2NhbExhc3ROQ291bnQpIHtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdmFyIHJlc291cmNlSmlkID0gbG9jYWxMYXN0TlNldFtpXTtcbiAgICAgICAgICAgIGlmIChuZXh0TG9jYWxMYXN0TlNldC5pbmRleE9mKHJlc291cmNlSmlkKSA9PT0gLTEpIHtcbiAgICAgICAgICAgICAgICBuZXh0TG9jYWxMYXN0TlNldC5wdXNoKHJlc291cmNlSmlkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGxvY2FsTGFzdE5TZXQgPSBuZXh0TG9jYWxMYXN0TlNldDtcblxuICAgICAgICB2YXIgdXBkYXRlTGFyZ2VWaWRlbyA9IGZhbHNlO1xuXG4gICAgICAgIC8vIEhhbmRsZSBMYXN0Ti9sb2NhbCBMYXN0TiBjaGFuZ2VzLlxuICAgICAgICAkKCcjcmVtb3RlVmlkZW9zPnNwYW4nKS5lYWNoKGZ1bmN0aW9uKCBpbmRleCwgZWxlbWVudCApIHtcbiAgICAgICAgICAgIHZhciByZXNvdXJjZUppZCA9IFZpZGVvTGF5b3V0LmdldFBlZXJDb250YWluZXJSZXNvdXJjZUppZChlbGVtZW50KTtcblxuICAgICAgICAgICAgdmFyIGlzUmVjZWl2ZWQgPSB0cnVlO1xuICAgICAgICAgICAgaWYgKHJlc291cmNlSmlkXG4gICAgICAgICAgICAgICAgJiYgbGFzdE5FbmRwb2ludHMuaW5kZXhPZihyZXNvdXJjZUppZCkgPCAwXG4gICAgICAgICAgICAgICAgJiYgbG9jYWxMYXN0TlNldC5pbmRleE9mKHJlc291cmNlSmlkKSA8IDApIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmxvZyhcIlJlbW92ZSBmcm9tIGxhc3QgTlwiLCByZXNvdXJjZUppZCk7XG4gICAgICAgICAgICAgICAgc2hvd1BlZXJDb250YWluZXIocmVzb3VyY2VKaWQsICdoaWRlJyk7XG4gICAgICAgICAgICAgICAgaXNSZWNlaXZlZCA9IGZhbHNlO1xuICAgICAgICAgICAgfSBlbHNlIGlmIChyZXNvdXJjZUppZFxuICAgICAgICAgICAgICAgICYmICQoJyNwYXJ0aWNpcGFudF8nICsgcmVzb3VyY2VKaWQpLmlzKCc6dmlzaWJsZScpXG4gICAgICAgICAgICAgICAgJiYgbGFzdE5FbmRwb2ludHMuaW5kZXhPZihyZXNvdXJjZUppZCkgPCAwXG4gICAgICAgICAgICAgICAgJiYgbG9jYWxMYXN0TlNldC5pbmRleE9mKHJlc291cmNlSmlkKSA+PSAwKSB7XG4gICAgICAgICAgICAgICAgc2hvd1BlZXJDb250YWluZXIocmVzb3VyY2VKaWQsICdhdmF0YXInKTtcbiAgICAgICAgICAgICAgICBpc1JlY2VpdmVkID0gZmFsc2U7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICghaXNSZWNlaXZlZCkge1xuICAgICAgICAgICAgICAgIC8vIHJlc291cmNlSmlkIGhhcyBkcm9wcGVkIG91dCBvZiB0aGUgc2VydmVyIHNpZGUgbGFzdE4gc2V0LCBzb1xuICAgICAgICAgICAgICAgIC8vIGl0IGlzIG5vIGxvbmdlciBiZWluZyByZWNlaXZlZC4gSWYgcmVzb3VyY2VKaWQgd2FzIGJlaW5nXG4gICAgICAgICAgICAgICAgLy8gZGlzcGxheWVkIGluIHRoZSBsYXJnZSB2aWRlbyB3ZSBoYXZlIHRvIHN3aXRjaCB0byBhbm90aGVyXG4gICAgICAgICAgICAgICAgLy8gdXNlci5cbiAgICAgICAgICAgICAgICB2YXIgbGFyZ2VWaWRlb1Jlc291cmNlID0gbGFyZ2VWaWRlb1N0YXRlLnVzZXJSZXNvdXJjZUppZDtcbiAgICAgICAgICAgICAgICBpZiAoIXVwZGF0ZUxhcmdlVmlkZW8gJiYgcmVzb3VyY2VKaWQgPT09IGxhcmdlVmlkZW9SZXNvdXJjZSkge1xuICAgICAgICAgICAgICAgICAgICB1cGRhdGVMYXJnZVZpZGVvID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuXG4gICAgICAgIGlmICghZW5kcG9pbnRzRW50ZXJpbmdMYXN0TiB8fCBlbmRwb2ludHNFbnRlcmluZ0xhc3ROLmxlbmd0aCA8IDApXG4gICAgICAgICAgICBlbmRwb2ludHNFbnRlcmluZ0xhc3ROID0gbGFzdE5FbmRwb2ludHM7XG5cbiAgICAgICAgaWYgKGVuZHBvaW50c0VudGVyaW5nTGFzdE4gJiYgZW5kcG9pbnRzRW50ZXJpbmdMYXN0Ti5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBlbmRwb2ludHNFbnRlcmluZ0xhc3ROLmZvckVhY2goZnVuY3Rpb24gKHJlc291cmNlSmlkKSB7XG5cbiAgICAgICAgICAgICAgICB2YXIgaXNWaXNpYmxlID0gJCgnI3BhcnRpY2lwYW50XycgKyByZXNvdXJjZUppZCkuaXMoJzp2aXNpYmxlJyk7XG4gICAgICAgICAgICAgICAgc2hvd1BlZXJDb250YWluZXIocmVzb3VyY2VKaWQsICdzaG93Jyk7XG4gICAgICAgICAgICAgICAgaWYgKCFpc1Zpc2libGUpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5sb2coXCJBZGQgdG8gbGFzdCBOXCIsIHJlc291cmNlSmlkKTtcblxuICAgICAgICAgICAgICAgICAgICB2YXIgamlkID0geG1wcC5maW5kSmlkRnJvbVJlc291cmNlKHJlc291cmNlSmlkKTtcbiAgICAgICAgICAgICAgICAgICAgdmFyIG1lZGlhU3RyZWFtID0gUlRDLnJlbW90ZVN0cmVhbXNbamlkXVtNZWRpYVN0cmVhbVR5cGUuVklERU9fVFlQRV07XG4gICAgICAgICAgICAgICAgICAgIHZhciBzZWwgPSAkKCcjcGFydGljaXBhbnRfJyArIHJlc291cmNlSmlkICsgJz52aWRlbycpO1xuXG4gICAgICAgICAgICAgICAgICAgIHZhciB2aWRlb1N0cmVhbSA9IHNpbXVsY2FzdC5nZXRSZWNlaXZpbmdWaWRlb1N0cmVhbShcbiAgICAgICAgICAgICAgICAgICAgICAgIG1lZGlhU3RyZWFtLnN0cmVhbSk7XG4gICAgICAgICAgICAgICAgICAgIFJUQy5hdHRhY2hNZWRpYVN0cmVhbShzZWwsIHZpZGVvU3RyZWFtKTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKGxhc3ROUGlja3VwSmlkID09IG1lZGlhU3RyZWFtLnBlZXJqaWQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIENsZWFuIHVwIHRoZSBsYXN0TiBwaWNrdXAgamlkLlxuICAgICAgICAgICAgICAgICAgICAgICAgbGFzdE5QaWNrdXBKaWQgPSBudWxsO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBEb24ndCBmaXJlIHRoZSBldmVudHMgYWdhaW4sIHRoZXkndmUgYWxyZWFkeVxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gYmVlbiBmaXJlZCBpbiB0aGUgY29udGFjdCBsaXN0IGNsaWNrIGhhbmRsZXIuXG4gICAgICAgICAgICAgICAgICAgICAgICBWaWRlb0xheW91dC5oYW5kbGVWaWRlb1RodW1iQ2xpY2tlZChcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAkKHNlbCkuYXR0cignc3JjJyksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQobWVkaWFTdHJlYW0ucGVlcmppZCkpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICB1cGRhdGVMYXJnZVZpZGVvID0gZmFsc2U7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgd2FpdEZvclJlbW90ZVZpZGVvKHNlbCwgbWVkaWFTdHJlYW0uc3NyYywgbWVkaWFTdHJlYW0uc3RyZWFtLCByZXNvdXJjZUppZCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSlcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFRoZSBlbmRwb2ludCB0aGF0IHdhcyBiZWluZyBzaG93biBpbiB0aGUgbGFyZ2UgdmlkZW8gaGFzIGRyb3BwZWQgb3V0XG4gICAgICAgIC8vIG9mIHRoZSBsYXN0TiBzZXQgYW5kIHRoZXJlIHdhcyBubyBsYXN0TiBwaWNrdXAgamlkLiBXZSBuZWVkIHRvIHVwZGF0ZVxuICAgICAgICAvLyB0aGUgbGFyZ2UgdmlkZW8gbm93LlxuXG4gICAgICAgIGlmICh1cGRhdGVMYXJnZVZpZGVvKSB7XG5cbiAgICAgICAgICAgIHZhciByZXNvdXJjZSwgY29udGFpbmVyLCBzcmM7XG4gICAgICAgICAgICB2YXIgbXlSZXNvdXJjZVxuICAgICAgICAgICAgICAgID0geG1wcC5teVJlc291cmNlKCk7XG5cbiAgICAgICAgICAgIC8vIEZpbmQgb3V0IHdoaWNoIGVuZHBvaW50IHRvIHNob3cgaW4gdGhlIGxhcmdlIHZpZGVvLlxuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBsYXN0TkVuZHBvaW50cy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIHJlc291cmNlID0gbGFzdE5FbmRwb2ludHNbaV07XG4gICAgICAgICAgICAgICAgaWYgKCFyZXNvdXJjZSB8fCByZXNvdXJjZSA9PT0gbXlSZXNvdXJjZSlcbiAgICAgICAgICAgICAgICAgICAgY29udGludWU7XG5cbiAgICAgICAgICAgICAgICBjb250YWluZXIgPSAkKFwiI3BhcnRpY2lwYW50X1wiICsgcmVzb3VyY2UpO1xuICAgICAgICAgICAgICAgIGlmIChjb250YWluZXIubGVuZ3RoID09IDApXG4gICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuXG4gICAgICAgICAgICAgICAgc3JjID0gJCgndmlkZW8nLCBjb250YWluZXIpLmF0dHIoJ3NyYycpO1xuICAgICAgICAgICAgICAgIGlmICghc3JjKVxuICAgICAgICAgICAgICAgICAgICBjb250aW51ZTtcblxuICAgICAgICAgICAgICAgIC8vIHZpZGVvU3JjVG9Tc3JjIG5lZWRzIHRvIGJlIHVwZGF0ZSBmb3IgdGhpcyBjYWxsIHRvIHN1Y2NlZWQuXG4gICAgICAgICAgICAgICAgVmlkZW9MYXlvdXQudXBkYXRlTGFyZ2VWaWRlbyhzcmMpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuXG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9KTtcblxuICAgICQoZG9jdW1lbnQpLmJpbmQoJ3NpbXVsY2FzdGxheWVyc2NoYW5naW5nJywgZnVuY3Rpb24gKGV2ZW50LCBlbmRwb2ludFNpbXVsY2FzdExheWVycykge1xuICAgICAgICBlbmRwb2ludFNpbXVsY2FzdExheWVycy5mb3JFYWNoKGZ1bmN0aW9uIChlc2wpIHtcblxuICAgICAgICAgICAgdmFyIHJlc291cmNlID0gZXNsLmVuZHBvaW50O1xuXG4gICAgICAgICAgICAvLyBpZiBsYXN0TiBpcyBlbmFibGVkICphbmQqIHRoZSBlbmRwb2ludCBpcyAqbm90KiBpbiB0aGUgbGFzdE4gc2V0LFxuICAgICAgICAgICAgLy8gdGhlbiBpZ25vcmUgdGhlIGV2ZW50ICg9IGRvIG5vdCBwcmVsb2FkIGFueXRoaW5nKS5cbiAgICAgICAgICAgIC8vXG4gICAgICAgICAgICAvLyBUaGUgYnJpZGdlIGNvdWxkIHByb2JhYmx5IHN0b3Agc2VuZGluZyB0aGlzIG1lc3NhZ2UgaWYgaXQncyBmb3JcbiAgICAgICAgICAgIC8vIGFuIGVuZHBvaW50IHRoYXQncyBub3QgaW4gbGFzdE4uXG5cbiAgICAgICAgICAgIGlmIChsYXN0TkNvdW50ICE9IC0xXG4gICAgICAgICAgICAgICAgJiYgKGxhc3ROQ291bnQgPCAxIHx8IGxhc3RORW5kcG9pbnRzQ2FjaGUuaW5kZXhPZihyZXNvdXJjZSkgPT09IC0xKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdmFyIHByaW1hcnlTU1JDID0gZXNsLnNpbXVsY2FzdExheWVyLnByaW1hcnlTU1JDO1xuXG4gICAgICAgICAgICAvLyBHZXQgc2Vzc2lvbiBhbmQgc3RyZWFtIGZyb20gcHJpbWFyeSBzc3JjLlxuICAgICAgICAgICAgdmFyIHJlcyA9IHNpbXVsY2FzdC5nZXRSZWNlaXZpbmdWaWRlb1N0cmVhbUJ5U1NSQyhwcmltYXJ5U1NSQyk7XG4gICAgICAgICAgICB2YXIgc2lkID0gcmVzLnNpZDtcbiAgICAgICAgICAgIHZhciBlbGVjdGVkU3RyZWFtID0gcmVzLnN0cmVhbTtcblxuICAgICAgICAgICAgaWYgKHNpZCAmJiBlbGVjdGVkU3RyZWFtKSB7XG4gICAgICAgICAgICAgICAgdmFyIG1zaWQgPSBzaW11bGNhc3QuZ2V0UmVtb3RlVmlkZW9TdHJlYW1JZEJ5U1NSQyhwcmltYXJ5U1NSQyk7XG5cbiAgICAgICAgICAgICAgICBjb25zb2xlLmluZm8oW2VzbCwgcHJpbWFyeVNTUkMsIG1zaWQsIHNpZCwgZWxlY3RlZFN0cmVhbV0pO1xuXG4gICAgICAgICAgICAgICAgdmFyIG1zaWRQYXJ0cyA9IG1zaWQuc3BsaXQoJyAnKTtcblxuICAgICAgICAgICAgICAgIHZhciBwcmVsb2FkID0gKFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKHNzcmMyamlkW3ByaW1hcnlTU1JDXSkgPT0gbGFyZ2VWaWRlb1N0YXRlLnVzZXJSZXNvdXJjZUppZCk7XG5cbiAgICAgICAgICAgICAgICBpZiAocHJlbG9hZCkge1xuICAgICAgICAgICAgICAgICAgICBpZiAobGFyZ2VWaWRlb1N0YXRlLnByZWxvYWQpXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICQobGFyZ2VWaWRlb1N0YXRlLnByZWxvYWQpLnJlbW92ZSgpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUuaW5mbygnUHJlbG9hZGluZyByZW1vdGUgdmlkZW8nKTtcbiAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLnByZWxvYWQgPSAkKCc8dmlkZW8gYXV0b3BsYXk+PC92aWRlbz4nKTtcbiAgICAgICAgICAgICAgICAgICAgLy8gc3NyY3MgYXJlIHVuaXF1ZSBpbiBhbiBydHAgc2Vzc2lvblxuICAgICAgICAgICAgICAgICAgICBsYXJnZVZpZGVvU3RhdGUucHJlbG9hZF9zc3JjID0gcHJpbWFyeVNTUkM7XG5cbiAgICAgICAgICAgICAgICAgICAgUlRDLmF0dGFjaE1lZGlhU3RyZWFtKGxhcmdlVmlkZW9TdGF0ZS5wcmVsb2FkLCBlbGVjdGVkU3RyZWFtKVxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKCdDb3VsZCBub3QgZmluZCBhIHN0cmVhbSBvciBhIHNlc3Npb24uJywgc2lkLCBlbGVjdGVkU3RyZWFtKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICAvKipcbiAgICAgKiBPbiBzaW11bGNhc3QgbGF5ZXJzIGNoYW5nZWQgZXZlbnQuXG4gICAgICovXG4gICAgJChkb2N1bWVudCkuYmluZCgnc2ltdWxjYXN0bGF5ZXJzY2hhbmdlZCcsIGZ1bmN0aW9uIChldmVudCwgZW5kcG9pbnRTaW11bGNhc3RMYXllcnMpIHtcbiAgICAgICAgZW5kcG9pbnRTaW11bGNhc3RMYXllcnMuZm9yRWFjaChmdW5jdGlvbiAoZXNsKSB7XG5cbiAgICAgICAgICAgIHZhciByZXNvdXJjZSA9IGVzbC5lbmRwb2ludDtcblxuICAgICAgICAgICAgLy8gaWYgbGFzdE4gaXMgZW5hYmxlZCAqYW5kKiB0aGUgZW5kcG9pbnQgaXMgKm5vdCogaW4gdGhlIGxhc3ROIHNldCxcbiAgICAgICAgICAgIC8vIHRoZW4gaWdub3JlIHRoZSBldmVudCAoPSBkbyBub3QgY2hhbmdlIGxhcmdlIHZpZGVvL3RodW1ibmFpbFxuICAgICAgICAgICAgLy8gU1JDcykuXG4gICAgICAgICAgICAvL1xuICAgICAgICAgICAgLy8gTm90ZSB0aGF0IGV2ZW4gaWYgd2UgaWdub3JlIHRoZSBcImNoYW5nZWRcIiBldmVudCBpbiB0aGlzIGV2ZW50XG4gICAgICAgICAgICAvLyBoYW5kbGVyLCB0aGUgYnJpZGdlIG11c3QgY29udGludWUgc2VuZGluZyB0aGVzZSBldmVudHMgYmVjYXVzZVxuICAgICAgICAgICAgLy8gdGhlIHNpbXVsY2FzdCBjb2RlIGluIHNpbXVsY2FzdC5qcyB1c2VzIGl0IHRvIGtub3cgd2hhdCdzIGdvaW5nXG4gICAgICAgICAgICAvLyB0byBiZSBzdHJlYW1lZCBieSB0aGUgYnJpZGdlIHdoZW4vaWYgdGhlIGVuZHBvaW50IGdldHMgYmFjayBpbnRvXG4gICAgICAgICAgICAvLyB0aGUgbGFzdE4gc2V0LlxuXG4gICAgICAgICAgICBpZiAobGFzdE5Db3VudCAhPSAtMVxuICAgICAgICAgICAgICAgICYmIChsYXN0TkNvdW50IDwgMSB8fCBsYXN0TkVuZHBvaW50c0NhY2hlLmluZGV4T2YocmVzb3VyY2UpID09PSAtMSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHZhciBwcmltYXJ5U1NSQyA9IGVzbC5zaW11bGNhc3RMYXllci5wcmltYXJ5U1NSQztcblxuICAgICAgICAgICAgLy8gR2V0IHNlc3Npb24gYW5kIHN0cmVhbSBmcm9tIHByaW1hcnkgc3NyYy5cbiAgICAgICAgICAgIHZhciByZXMgPSBzaW11bGNhc3QuZ2V0UmVjZWl2aW5nVmlkZW9TdHJlYW1CeVNTUkMocHJpbWFyeVNTUkMpO1xuICAgICAgICAgICAgdmFyIHNpZCA9IHJlcy5zaWQ7XG4gICAgICAgICAgICB2YXIgZWxlY3RlZFN0cmVhbSA9IHJlcy5zdHJlYW07XG5cbiAgICAgICAgICAgIGlmIChzaWQgJiYgZWxlY3RlZFN0cmVhbSkge1xuICAgICAgICAgICAgICAgIHZhciBtc2lkID0gc2ltdWxjYXN0LmdldFJlbW90ZVZpZGVvU3RyZWFtSWRCeVNTUkMocHJpbWFyeVNTUkMpO1xuXG4gICAgICAgICAgICAgICAgY29uc29sZS5pbmZvKCdTd2l0Y2hpbmcgc2ltdWxjYXN0IHN1YnN0cmVhbS4nKTtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmluZm8oW2VzbCwgcHJpbWFyeVNTUkMsIG1zaWQsIHNpZCwgZWxlY3RlZFN0cmVhbV0pO1xuXG4gICAgICAgICAgICAgICAgdmFyIG1zaWRQYXJ0cyA9IG1zaWQuc3BsaXQoJyAnKTtcbiAgICAgICAgICAgICAgICB2YXIgc2VsUmVtb3RlVmlkZW8gPSAkKFsnIycsICdyZW1vdGVWaWRlb18nLCBzaWQsICdfJywgbXNpZFBhcnRzWzBdXS5qb2luKCcnKSk7XG5cbiAgICAgICAgICAgICAgICB2YXIgdXBkYXRlTGFyZ2VWaWRlbyA9IChTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChzc3JjMmppZFtwcmltYXJ5U1NSQ10pXG4gICAgICAgICAgICAgICAgICAgID09IGxhcmdlVmlkZW9TdGF0ZS51c2VyUmVzb3VyY2VKaWQpO1xuICAgICAgICAgICAgICAgIHZhciB1cGRhdGVGb2N1c2VkVmlkZW9TcmMgPSAoZm9jdXNlZFZpZGVvSW5mbyAmJiBmb2N1c2VkVmlkZW9JbmZvLnNyYyAmJiBmb2N1c2VkVmlkZW9JbmZvLnNyYyAhPSAnJyAmJlxuICAgICAgICAgICAgICAgICAgICAoUlRDLmdldFZpZGVvU3JjKHNlbFJlbW90ZVZpZGVvWzBdKSA9PSBmb2N1c2VkVmlkZW9JbmZvLnNyYykpO1xuXG4gICAgICAgICAgICAgICAgdmFyIGVsZWN0ZWRTdHJlYW1Vcmw7XG4gICAgICAgICAgICAgICAgaWYgKGxhcmdlVmlkZW9TdGF0ZS5wcmVsb2FkX3NzcmMgPT0gcHJpbWFyeVNTUkMpXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICBSVEMuc2V0VmlkZW9TcmMoc2VsUmVtb3RlVmlkZW9bMF0sIFJUQy5nZXRWaWRlb1NyYyhsYXJnZVZpZGVvU3RhdGUucHJlbG9hZFswXSkpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICBpZiAobGFyZ2VWaWRlb1N0YXRlLnByZWxvYWRcbiAgICAgICAgICAgICAgICAgICAgICAgICYmIGxhcmdlVmlkZW9TdGF0ZS5wcmVsb2FkICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICQobGFyZ2VWaWRlb1N0YXRlLnByZWxvYWQpLnJlbW92ZSgpO1xuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgbGFyZ2VWaWRlb1N0YXRlLnByZWxvYWRfc3NyYyA9IDA7XG5cbiAgICAgICAgICAgICAgICAgICAgUlRDLmF0dGFjaE1lZGlhU3RyZWFtKHNlbFJlbW90ZVZpZGVvLCBlbGVjdGVkU3RyZWFtKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICB2YXIgamlkID0gc3NyYzJqaWRbcHJpbWFyeVNTUkNdO1xuICAgICAgICAgICAgICAgIGppZDJTc3JjW2ppZF0gPSBwcmltYXJ5U1NSQztcblxuICAgICAgICAgICAgICAgIGlmICh1cGRhdGVMYXJnZVZpZGVvKSB7XG4gICAgICAgICAgICAgICAgICAgIFZpZGVvTGF5b3V0LnVwZGF0ZUxhcmdlVmlkZW8oUlRDLmdldFZpZGVvU3JjKHNlbFJlbW90ZVZpZGVvWzBdKSwgbnVsbCxcbiAgICAgICAgICAgICAgICAgICAgICAgIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCkpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGlmICh1cGRhdGVGb2N1c2VkVmlkZW9TcmMpIHtcbiAgICAgICAgICAgICAgICAgICAgZm9jdXNlZFZpZGVvSW5mby5zcmMgPSBSVEMuZ2V0VmlkZW9TcmMoc2VsUmVtb3RlVmlkZW9bMF0pO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHZhciB2aWRlb0lkO1xuICAgICAgICAgICAgICAgIGlmKHJlc291cmNlID09IHhtcHAubXlSZXNvdXJjZSgpKVxuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgdmlkZW9JZCA9IFwibG9jYWxWaWRlb0NvbnRhaW5lclwiO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICB2aWRlb0lkID0gXCJwYXJ0aWNpcGFudF9cIiArIHJlc291cmNlO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB2YXIgY29ubmVjdGlvbkluZGljYXRvciA9IFZpZGVvTGF5b3V0LmNvbm5lY3Rpb25JbmRpY2F0b3JzW3ZpZGVvSWRdO1xuICAgICAgICAgICAgICAgIGlmKGNvbm5lY3Rpb25JbmRpY2F0b3IpXG4gICAgICAgICAgICAgICAgICAgIGNvbm5lY3Rpb25JbmRpY2F0b3IudXBkYXRlUG9wb3ZlckRhdGEoKTtcblxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKCdDb3VsZCBub3QgZmluZCBhIHN0cmVhbSBvciBhIHNpZC4nLCBzaWQsIGVsZWN0ZWRTdHJlYW0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9KTtcblxuICAgIC8qKlxuICAgICAqIFVwZGF0ZXMgbG9jYWwgc3RhdHNcbiAgICAgKiBAcGFyYW0gcGVyY2VudFxuICAgICAqIEBwYXJhbSBvYmplY3RcbiAgICAgKi9cbiAgICBteS51cGRhdGVMb2NhbENvbm5lY3Rpb25TdGF0cyA9IGZ1bmN0aW9uIChwZXJjZW50LCBvYmplY3QpIHtcbiAgICAgICAgdmFyIHJlc29sdXRpb24gPSBudWxsO1xuICAgICAgICBpZihvYmplY3QucmVzb2x1dGlvbiAhPT0gbnVsbClcbiAgICAgICAge1xuICAgICAgICAgICAgcmVzb2x1dGlvbiA9IG9iamVjdC5yZXNvbHV0aW9uO1xuICAgICAgICAgICAgb2JqZWN0LnJlc29sdXRpb24gPSByZXNvbHV0aW9uW3htcHAubXlKaWQoKV07XG4gICAgICAgICAgICBkZWxldGUgcmVzb2x1dGlvblt4bXBwLm15SmlkKCldO1xuICAgICAgICB9XG4gICAgICAgIHVwZGF0ZVN0YXRzSW5kaWNhdG9yKFwibG9jYWxWaWRlb0NvbnRhaW5lclwiLCBwZXJjZW50LCBvYmplY3QpO1xuICAgICAgICBmb3IodmFyIGppZCBpbiByZXNvbHV0aW9uKVxuICAgICAgICB7XG4gICAgICAgICAgICBpZihyZXNvbHV0aW9uW2ppZF0gPT09IG51bGwpXG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB2YXIgaWQgPSAncGFydGljaXBhbnRfJyArIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCk7XG4gICAgICAgICAgICBpZihWaWRlb0xheW91dC5jb25uZWN0aW9uSW5kaWNhdG9yc1tpZF0pXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgVmlkZW9MYXlvdXQuY29ubmVjdGlvbkluZGljYXRvcnNbaWRdLnVwZGF0ZVJlc29sdXRpb24ocmVzb2x1dGlvbltqaWRdKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIFVwZGF0ZXMgcmVtb3RlIHN0YXRzLlxuICAgICAqIEBwYXJhbSBqaWQgdGhlIGppZCBhc3NvY2lhdGVkIHdpdGggdGhlIHN0YXRzXG4gICAgICogQHBhcmFtIHBlcmNlbnQgdGhlIGNvbm5lY3Rpb24gcXVhbGl0eSBwZXJjZW50XG4gICAgICogQHBhcmFtIG9iamVjdCB0aGUgc3RhdHMgZGF0YVxuICAgICAqL1xuICAgIG15LnVwZGF0ZUNvbm5lY3Rpb25TdGF0cyA9IGZ1bmN0aW9uIChqaWQsIHBlcmNlbnQsIG9iamVjdCkge1xuICAgICAgICB2YXIgcmVzb3VyY2VKaWQgPSBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChqaWQpO1xuXG4gICAgICAgIHZhciB2aWRlb1NwYW5JZCA9ICdwYXJ0aWNpcGFudF8nICsgcmVzb3VyY2VKaWQ7XG4gICAgICAgIHVwZGF0ZVN0YXRzSW5kaWNhdG9yKHZpZGVvU3BhbklkLCBwZXJjZW50LCBvYmplY3QpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBSZW1vdmVzIHRoZSBjb25uZWN0aW9uXG4gICAgICogQHBhcmFtIGppZFxuICAgICAqL1xuICAgIG15LnJlbW92ZUNvbm5lY3Rpb25JbmRpY2F0b3IgPSBmdW5jdGlvbiAoamlkKSB7XG4gICAgICAgIGlmKFZpZGVvTGF5b3V0LmNvbm5lY3Rpb25JbmRpY2F0b3JzWydwYXJ0aWNpcGFudF8nICsgU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKV0pXG4gICAgICAgICAgICBWaWRlb0xheW91dC5jb25uZWN0aW9uSW5kaWNhdG9yc1sncGFydGljaXBhbnRfJyArIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCldLnJlbW92ZSgpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBIaWRlcyB0aGUgY29ubmVjdGlvbiBpbmRpY2F0b3JcbiAgICAgKiBAcGFyYW0gamlkXG4gICAgICovXG4gICAgbXkuaGlkZUNvbm5lY3Rpb25JbmRpY2F0b3IgPSBmdW5jdGlvbiAoamlkKSB7XG4gICAgICAgIGlmKFZpZGVvTGF5b3V0LmNvbm5lY3Rpb25JbmRpY2F0b3JzWydwYXJ0aWNpcGFudF8nICsgU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQoamlkKV0pXG4gICAgICAgICAgICBWaWRlb0xheW91dC5jb25uZWN0aW9uSW5kaWNhdG9yc1sncGFydGljaXBhbnRfJyArIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCldLmhpZGUoKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogSGlkZXMgYWxsIHRoZSBpbmRpY2F0b3JzXG4gICAgICovXG4gICAgbXkub25TdGF0c1N0b3AgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGZvcih2YXIgaW5kaWNhdG9yIGluIFZpZGVvTGF5b3V0LmNvbm5lY3Rpb25JbmRpY2F0b3JzKVxuICAgICAgICB7XG4gICAgICAgICAgICBWaWRlb0xheW91dC5jb25uZWN0aW9uSW5kaWNhdG9yc1tpbmRpY2F0b3JdLmhpZGVJbmRpY2F0b3IoKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICByZXR1cm4gbXk7XG59KFZpZGVvTGF5b3V0IHx8IHt9KSk7XG5cbm1vZHVsZS5leHBvcnRzID0gVmlkZW9MYXlvdXQ7IiwiLy92YXIgbm91bnMgPSBbXG4vL107XG52YXIgcGx1cmFsTm91bnMgPSBbXG4gICAgXCJBbGllbnNcIiwgXCJBbmltYWxzXCIsIFwiQW50ZWxvcGVzXCIsIFwiQW50c1wiLCBcIkFwZXNcIiwgXCJBcHBsZXNcIiwgXCJCYWJvb25zXCIsIFwiQmFjdGVyaWFcIiwgXCJCYWRnZXJzXCIsIFwiQmFuYW5hc1wiLCBcIkJhdHNcIixcbiAgICBcIkJlYXJzXCIsIFwiQmlyZHNcIiwgXCJCb25vYm9zXCIsIFwiQnJpZGVzXCIsIFwiQnVnc1wiLCBcIkJ1bGxzXCIsIFwiQnV0dGVyZmxpZXNcIiwgXCJDaGVldGFoc1wiLFxuICAgIFwiQ2hlcnJpZXNcIiwgXCJDaGlja2VuXCIsIFwiQ2hpbGRyZW5cIiwgXCJDaGltcHNcIiwgXCJDbG93bnNcIiwgXCJDb3dzXCIsIFwiQ3JlYXR1cmVzXCIsIFwiRGlub3NhdXJzXCIsIFwiRG9nc1wiLCBcIkRvbHBoaW5zXCIsXG4gICAgXCJEb25rZXlzXCIsIFwiRHJhZ29uc1wiLCBcIkR1Y2tzXCIsIFwiRHdhcmZzXCIsIFwiRWFnbGVzXCIsIFwiRWxlcGhhbnRzXCIsIFwiRWx2ZXNcIiwgXCJGQUlMXCIsIFwiRmF0aGVyc1wiLFxuICAgIFwiRmlzaFwiLCBcIkZsb3dlcnNcIiwgXCJGcm9nc1wiLCBcIkZydWl0XCIsIFwiRnVuZ2lcIiwgXCJHYWxheGllc1wiLCBcIkdlZXNlXCIsIFwiR29hdHNcIixcbiAgICBcIkdvcmlsbGFzXCIsIFwiSGVkZ2Vob2dzXCIsIFwiSGlwcG9zXCIsIFwiSG9yc2VzXCIsIFwiSHVudGVyc1wiLCBcIkluc2VjdHNcIiwgXCJLaWRzXCIsIFwiS25pZ2h0c1wiLFxuICAgIFwiTGVtb25zXCIsIFwiTGVtdXJzXCIsIFwiTGVvcGFyZHNcIiwgXCJMaWZlRm9ybXNcIiwgXCJMaW9uc1wiLCBcIkxpemFyZHNcIiwgXCJNaWNlXCIsIFwiTW9ua2V5c1wiLCBcIk1vbnN0ZXJzXCIsXG4gICAgXCJNdXNocm9vbXNcIiwgXCJPY3RvcG9kZXNcIiwgXCJPcmFuZ2VzXCIsIFwiT3Jhbmd1dGFuc1wiLCBcIk9yZ2FuaXNtc1wiLCBcIlBhbnRzXCIsIFwiUGFycm90c1wiLCBcIlBlbmd1aW5zXCIsXG4gICAgXCJQZW9wbGVcIiwgXCJQaWdlb25zXCIsIFwiUGlnc1wiLCBcIlBpbmVhcHBsZXNcIiwgXCJQbGFudHNcIiwgXCJQb3RhdG9lc1wiLCBcIlByaWVzdHNcIiwgXCJSYXRzXCIsIFwiUmVwdGlsZXNcIiwgXCJSZXB0aWxpYW5zXCIsXG4gICAgXCJSaGlub3NcIiwgXCJTZWFndWxsc1wiLCBcIlNoZWVwXCIsIFwiU2libGluZ3NcIiwgXCJTbmFrZXNcIiwgXCJTcGFnaGV0dGlcIiwgXCJTcGlkZXJzXCIsIFwiU3F1aWRcIiwgXCJTcXVpcnJlbHNcIixcbiAgICBcIlN0YXJzXCIsIFwiU3R1ZGVudHNcIiwgXCJUZWFjaGVyc1wiLCBcIlRpZ2Vyc1wiLCBcIlRvbWF0b2VzXCIsIFwiVHJlZXNcIiwgXCJWYW1waXJlc1wiLCBcIlZlZ2V0YWJsZXNcIiwgXCJWaXJ1c2VzXCIsIFwiVnVsY2Fuc1wiLFxuICAgIFwiV2FyZXdvbHZlc1wiLCBcIldlYXNlbHNcIiwgXCJXaGFsZXNcIiwgXCJXaXRjaGVzXCIsIFwiV2l6YXJkc1wiLCBcIldvbHZlc1wiLCBcIldvcmtlcnNcIiwgXCJXb3Jtc1wiLCBcIlplYnJhc1wiXG5dO1xuLy92YXIgcGxhY2VzID0gW1xuLy9cIlB1YlwiLCBcIlVuaXZlcnNpdHlcIiwgXCJBaXJwb3J0XCIsIFwiTGlicmFyeVwiLCBcIk1hbGxcIiwgXCJUaGVhdGVyXCIsIFwiU3RhZGl1bVwiLCBcIk9mZmljZVwiLCBcIlNob3dcIiwgXCJHYWxsb3dzXCIsIFwiQmVhY2hcIixcbi8vIFwiQ2VtZXRlcnlcIiwgXCJIb3NwaXRhbFwiLCBcIlJlY2VwdGlvblwiLCBcIlJlc3RhdXJhbnRcIiwgXCJCYXJcIiwgXCJDaHVyY2hcIiwgXCJIb3VzZVwiLCBcIlNjaG9vbFwiLCBcIlNxdWFyZVwiLCBcIlZpbGxhZ2VcIixcbi8vIFwiQ2luZW1hXCIsIFwiTW92aWVzXCIsIFwiUGFydHlcIiwgXCJSZXN0cm9vbVwiLCBcIkVuZFwiLCBcIkphaWxcIiwgXCJQb3N0T2ZmaWNlXCIsIFwiU3RhdGlvblwiLCBcIkNpcmN1c1wiLCBcIkdhdGVzXCIsIFwiRW50cmFuY2VcIixcbi8vIFwiQnJpZGdlXCJcbi8vXTtcbnZhciB2ZXJicyA9IFtcbiAgICBcIkFiYW5kb25cIiwgXCJBZGFwdFwiLCBcIkFkdmVydGlzZVwiLCBcIkFuc3dlclwiLCBcIkFudGljaXBhdGVcIiwgXCJBcHByZWNpYXRlXCIsXG4gICAgXCJBcHByb2FjaFwiLCBcIkFyZ3VlXCIsIFwiQXNrXCIsIFwiQml0ZVwiLCBcIkJsb3Nzb21cIiwgXCJCbHVzaFwiLCBcIkJyZWF0aGVcIiwgXCJCcmVlZFwiLCBcIkJyaWJlXCIsIFwiQnVyblwiLCBcIkNhbGN1bGF0ZVwiLFxuICAgIFwiQ2xlYW5cIiwgXCJDb2RlXCIsIFwiQ29tbXVuaWNhdGVcIiwgXCJDb21wdXRlXCIsIFwiQ29uZmVzc1wiLCBcIkNvbmZpc2NhdGVcIiwgXCJDb25qdWdhdGVcIiwgXCJDb25qdXJlXCIsIFwiQ29uc3VtZVwiLFxuICAgIFwiQ29udGVtcGxhdGVcIiwgXCJDcmF3bFwiLCBcIkRhbmNlXCIsIFwiRGVsZWdhdGVcIiwgXCJEZXZvdXJcIiwgXCJEZXZlbG9wXCIsIFwiRGlmZmVyXCIsIFwiRGlzY3Vzc1wiLFxuICAgIFwiRGlzc29sdmVcIiwgXCJEcmlua1wiLCBcIkVhdFwiLCBcIkVsYWJvcmF0ZVwiLCBcIkVtYW5jaXBhdGVcIiwgXCJFc3RpbWF0ZVwiLCBcIkV4cGlyZVwiLCBcIkV4dGluZ3Vpc2hcIixcbiAgICBcIkV4dHJhY3RcIiwgXCJGQUlMXCIsIFwiRmFjaWxpdGF0ZVwiLCBcIkZhbGxcIiwgXCJGZWVkXCIsIFwiRmluaXNoXCIsIFwiRmxvc3NcIiwgXCJGbHlcIiwgXCJGb2xsb3dcIiwgXCJGcmFnbWVudFwiLCBcIkZyZWV6ZVwiLFxuICAgIFwiR2F0aGVyXCIsIFwiR2xvd1wiLCBcIkdyb3dcIiwgXCJIZXhcIiwgXCJIaWRlXCIsIFwiSHVnXCIsIFwiSHVycnlcIiwgXCJJbXByb3ZlXCIsIFwiSW50ZXJzZWN0XCIsIFwiSW52ZXN0aWdhdGVcIiwgXCJKaW54XCIsXG4gICAgXCJKb2tlXCIsIFwiSnViaWxhdGVcIiwgXCJLaXNzXCIsIFwiTGF1Z2hcIiwgXCJNYW5hZ2VcIiwgXCJNZWV0XCIsIFwiTWVyZ2VcIiwgXCJNb3ZlXCIsIFwiT2JqZWN0XCIsIFwiT2JzZXJ2ZVwiLCBcIk9mZmVyXCIsXG4gICAgXCJQYWludFwiLCBcIlBhcnRpY2lwYXRlXCIsIFwiUGFydHlcIiwgXCJQZXJmb3JtXCIsIFwiUGxhblwiLCBcIlB1cnN1ZVwiLCBcIlBpZXJjZVwiLCBcIlBsYXlcIiwgXCJQb3N0cG9uZVwiLCBcIlByYXlcIiwgXCJQcm9jbGFpbVwiLFxuICAgIFwiUXVlc3Rpb25cIiwgXCJSZWFkXCIsIFwiUmVja29uXCIsIFwiUmVqb2ljZVwiLCBcIlJlcHJlc2VudFwiLCBcIlJlc2l6ZVwiLCBcIlJoeW1lXCIsIFwiU2NyZWFtXCIsIFwiU2VhcmNoXCIsIFwiU2VsZWN0XCIsIFwiU2hhcmVcIiwgXCJTaG9vdFwiLFxuICAgIFwiU2hvdXRcIiwgXCJTaWduYWxcIiwgXCJTaW5nXCIsIFwiU2thdGVcIiwgXCJTbGVlcFwiLCBcIlNtaWxlXCIsIFwiU21va2VcIiwgXCJTb2x2ZVwiLCBcIlNwZWxsXCIsIFwiU3RlZXJcIiwgXCJTdGlua1wiLFxuICAgIFwiU3Vic3RpdHV0ZVwiLCBcIlN3aW1cIiwgXCJUYXN0ZVwiLCBcIlRlYWNoXCIsIFwiVGVybWluYXRlXCIsIFwiVGhpbmtcIiwgXCJUeXBlXCIsIFwiVW5pdGVcIiwgXCJWYW5pc2hcIiwgXCJXb3JzaGlwXCJcbl07XG52YXIgYWR2ZXJicyA9IFtcbiAgICBcIkFic2VudGx5XCIsIFwiQWNjdXJhdGVseVwiLCBcIkFjY3VzaW5nbHlcIiwgXCJBZG9yYWJseVwiLCBcIkFsbFRoZVRpbWVcIiwgXCJBbG9uZVwiLCBcIkFsd2F5c1wiLCBcIkFtYXppbmdseVwiLCBcIkFuZ3JpbHlcIixcbiAgICBcIkFueGlvdXNseVwiLCBcIkFueXdoZXJlXCIsIFwiQXBwYWxsaW5nbHlcIiwgXCJBcHBhcmVudGx5XCIsIFwiQXJ0aWN1bGF0ZWx5XCIsIFwiQXN0b25pc2hpbmdseVwiLCBcIkJhZGx5XCIsIFwiQmFyZWx5XCIsXG4gICAgXCJCZWF1dGlmdWxseVwiLCBcIkJsaW5kbHlcIiwgXCJCcmF2ZWx5XCIsIFwiQnJpZ2h0bHlcIiwgXCJCcmlza2x5XCIsIFwiQnJ1dGFsbHlcIiwgXCJDYWxtbHlcIiwgXCJDYXJlZnVsbHlcIiwgXCJDYXN1YWxseVwiLFxuICAgIFwiQ2F1dGlvdXNseVwiLCBcIkNsZXZlcmx5XCIsIFwiQ29uc3RhbnRseVwiLCBcIkNvcnJlY3RseVwiLCBcIkNyYXppbHlcIiwgXCJDdXJpb3VzbHlcIiwgXCJDeW5pY2FsbHlcIiwgXCJEYWlseVwiLFxuICAgIFwiRGFuZ2Vyb3VzbHlcIiwgXCJEZWxpYmVyYXRlbHlcIiwgXCJEZWxpY2F0ZWx5XCIsIFwiRGVzcGVyYXRlbHlcIiwgXCJEaXNjcmVldGx5XCIsIFwiRWFnZXJseVwiLCBcIkVhc2lseVwiLCBcIkV1cGhvcmljbHlcIixcbiAgICBcIkV2ZW5seVwiLCBcIkV2ZXJ5d2hlcmVcIiwgXCJFeGFjdGx5XCIsIFwiRXhwZWN0YW50bHlcIiwgXCJFeHRlbnNpdmVseVwiLCBcIkZBSUxcIiwgXCJGZXJvY2lvdXNseVwiLCBcIkZpZXJjZWx5XCIsIFwiRmluZWx5XCIsXG4gICAgXCJGbGF0bHlcIiwgXCJGcmVxdWVudGx5XCIsIFwiRnJpZ2h0ZW5pbmdseVwiLCBcIkdlbnRseVwiLCBcIkdsb3Jpb3VzbHlcIiwgXCJHcmltbHlcIiwgXCJHdWlsdGlseVwiLCBcIkhhcHBpbHlcIixcbiAgICBcIkhhcmRcIiwgXCJIYXN0aWx5XCIsIFwiSGVyb2ljYWxseVwiLCBcIkhpZ2hcIiwgXCJIaWdobHlcIiwgXCJIb3VybHlcIiwgXCJIdW1ibHlcIiwgXCJIeXN0ZXJpY2FsbHlcIiwgXCJJbW1lbnNlbHlcIixcbiAgICBcIkltcGFydGlhbGx5XCIsIFwiSW1wb2xpdGVseVwiLCBcIkluZGlmZmVyZW50bHlcIiwgXCJJbnRlbnNlbHlcIiwgXCJKZWFsb3VzbHlcIiwgXCJKb3ZpYWxseVwiLCBcIktpbmRseVwiLCBcIkxhemlseVwiLFxuICAgIFwiTGlnaHRseVwiLCBcIkxvdWRseVwiLCBcIkxvdmluZ2x5XCIsIFwiTG95YWxseVwiLCBcIk1hZ25pZmljZW50bHlcIiwgXCJNYWxldm9sZW50bHlcIiwgXCJNZXJyaWx5XCIsIFwiTWlnaHRpbHlcIiwgXCJNaXNlcmFibHlcIixcbiAgICBcIk15c3RlcmlvdXNseVwiLCBcIk5PVFwiLCBcIk5lcnZvdXNseVwiLCBcIk5pY2VseVwiLCBcIk5vd2hlcmVcIiwgXCJPYmplY3RpdmVseVwiLCBcIk9ibm94aW91c2x5XCIsIFwiT2JzZXNzaXZlbHlcIixcbiAgICBcIk9idmlvdXNseVwiLCBcIk9mdGVuXCIsIFwiUGFpbmZ1bGx5XCIsIFwiUGF0aWVudGx5XCIsIFwiUGxheWZ1bGx5XCIsIFwiUG9saXRlbHlcIiwgXCJQb29ybHlcIiwgXCJQcmVjaXNlbHlcIiwgXCJQcm9tcHRseVwiLFxuICAgIFwiUXVpY2tseVwiLCBcIlF1aWV0bHlcIiwgXCJSYW5kb21seVwiLCBcIlJhcGlkbHlcIiwgXCJSYXJlbHlcIiwgXCJSZWNrbGVzc2x5XCIsIFwiUmVndWxhcmx5XCIsIFwiUmVtb3JzZWZ1bGx5XCIsIFwiUmVzcG9uc2libHlcIixcbiAgICBcIlJ1ZGVseVwiLCBcIlJ1dGhsZXNzbHlcIiwgXCJTYWRseVwiLCBcIlNjb3JuZnVsbHlcIiwgXCJTZWFtbGVzc2x5XCIsIFwiU2VsZG9tXCIsIFwiU2VsZmlzaGx5XCIsIFwiU2VyaW91c2x5XCIsIFwiU2hha2lseVwiLFxuICAgIFwiU2hhcnBseVwiLCBcIlNpZGV3YXlzXCIsIFwiU2lsZW50bHlcIiwgXCJTbGVlcGlseVwiLCBcIlNsaWdodGx5XCIsIFwiU2xvd2x5XCIsIFwiU2x5bHlcIiwgXCJTbW9vdGhseVwiLCBcIlNvZnRseVwiLCBcIlNvbGVtbmx5XCIsIFwiU3RlYWRpbHlcIiwgXCJTdGVybmx5XCIsIFwiU3RyYW5nZWx5XCIsIFwiU3Ryb25nbHlcIiwgXCJTdHVubmluZ2x5XCIsIFwiU3VyZWx5XCIsIFwiVGVuZGVybHlcIiwgXCJUaG91Z2h0ZnVsbHlcIixcbiAgICBcIlRpZ2h0bHlcIiwgXCJVbmVhc2lseVwiLCBcIlZhbmlzaGluZ2x5XCIsIFwiVmlvbGVudGx5XCIsIFwiV2FybWx5XCIsIFwiV2Vha2x5XCIsIFwiV2VhcmlseVwiLCBcIldlZWtseVwiLCBcIldlaXJkbHlcIiwgXCJXZWxsXCIsXG4gICAgXCJXZWxsXCIsIFwiV2lja2VkbHlcIiwgXCJXaWxkbHlcIiwgXCJXaXNlbHlcIiwgXCJXb25kZXJmdWxseVwiLCBcIlllYXJseVwiXG5dO1xudmFyIGFkamVjdGl2ZXMgPSBbXG4gICAgXCJBYm9taW5hYmxlXCIsIFwiQWNjdXJhdGVcIiwgXCJBZG9yYWJsZVwiLCBcIkFsbFwiLCBcIkFsbGVnZWRcIiwgXCJBbmNpZW50XCIsIFwiQW5ncnlcIiwgXCJBbmdyeVwiLCBcIkFueGlvdXNcIiwgXCJBcHBhbGxpbmdcIixcbiAgICBcIkFwcGFyZW50XCIsIFwiQXN0b25pc2hpbmdcIiwgXCJBdHRyYWN0aXZlXCIsIFwiQXdlc29tZVwiLCBcIkJhYnlcIiwgXCJCYWRcIiwgXCJCZWF1dGlmdWxcIiwgXCJCZW5pZ25cIiwgXCJCaWdcIiwgXCJCaXR0ZXJcIixcbiAgICBcIkJsaW5kXCIsIFwiQmx1ZVwiLCBcIkJvbGRcIiwgXCJCcmF2ZVwiLCBcIkJyaWdodFwiLCBcIkJyaXNrXCIsIFwiQ2FsbVwiLCBcIkNhbW91ZmxhZ2VkXCIsIFwiQ2FzdWFsXCIsIFwiQ2F1dGlvdXNcIixcbiAgICBcIkNob3BweVwiLCBcIkNob3NlblwiLCBcIkNsZXZlclwiLCBcIkNvbGRcIiwgXCJDb29sXCIsIFwiQ3Jhd2x5XCIsIFwiQ3JhenlcIiwgXCJDcmVlcHlcIiwgXCJDcnVlbFwiLCBcIkN1cmlvdXNcIiwgXCJDeW5pY2FsXCIsXG4gICAgXCJEYW5nZXJvdXNcIiwgXCJEYXJrXCIsIFwiRGVsaWNhdGVcIiwgXCJEZXNwZXJhdGVcIiwgXCJEaWZmaWN1bHRcIiwgXCJEaXNjcmVldFwiLCBcIkRpc2d1aXNlZFwiLCBcIkRpenp5XCIsXG4gICAgXCJEdW1iXCIsIFwiRWFnZXJcIiwgXCJFYXN5XCIsIFwiRWRneVwiLCBcIkVsZWN0cmljXCIsIFwiRWxlZ2FudFwiLCBcIkVtYW5jaXBhdGVkXCIsIFwiRW5vcm1vdXNcIiwgXCJFdXBob3JpY1wiLCBcIkV2aWxcIixcbiAgICBcIkZBSUxcIiwgXCJGYXN0XCIsIFwiRmVyb2Npb3VzXCIsIFwiRmllcmNlXCIsIFwiRmluZVwiLCBcIkZsYXdlZFwiLCBcIkZseWluZ1wiLCBcIkZvb2xpc2hcIiwgXCJGb3h5XCIsXG4gICAgXCJGcmVlemluZ1wiLCBcIkZ1bm55XCIsIFwiRnVyaW91c1wiLCBcIkdlbnRsZVwiLCBcIkdsb3Jpb3VzXCIsIFwiR29sZGVuXCIsIFwiR29vZFwiLCBcIkdyZWVuXCIsIFwiR3JlZW5cIiwgXCJHdWlsdHlcIixcbiAgICBcIkhhaXJ5XCIsIFwiSGFwcHlcIiwgXCJIYXJkXCIsIFwiSGFzdHlcIiwgXCJIYXp5XCIsIFwiSGVyb2ljXCIsIFwiSG9zdGlsZVwiLCBcIkhvdFwiLCBcIkh1bWJsZVwiLCBcIkh1bW9uZ291c1wiLFxuICAgIFwiSHVtb3JvdXNcIiwgXCJIeXN0ZXJpY2FsXCIsIFwiSWRlYWxpc3RpY1wiLCBcIklnbm9yYW50XCIsIFwiSW1tZW5zZVwiLCBcIkltcGFydGlhbFwiLCBcIkltcG9saXRlXCIsIFwiSW5kaWZmZXJlbnRcIixcbiAgICBcIkluZnVyaWF0ZWRcIiwgXCJJbnNpZ2h0ZnVsXCIsIFwiSW50ZW5zZVwiLCBcIkludGVyZXN0aW5nXCIsIFwiSW50aW1pZGF0ZWRcIiwgXCJJbnRyaWd1aW5nXCIsIFwiSmVhbG91c1wiLCBcIkpvbGx5XCIsIFwiSm92aWFsXCIsXG4gICAgXCJKdW1weVwiLCBcIktpbmRcIiwgXCJMYXVnaGluZ1wiLCBcIkxhenlcIiwgXCJMaXF1aWRcIiwgXCJMb25lbHlcIiwgXCJMb25naW5nXCIsIFwiTG91ZFwiLCBcIkxvdmluZ1wiLCBcIkxveWFsXCIsIFwiTWFjYWJyZVwiLCBcIk1hZFwiLFxuICAgIFwiTWFnaWNhbFwiLCBcIk1hZ25pZmljZW50XCIsIFwiTWFsZXZvbGVudFwiLCBcIk1lZGlldmFsXCIsIFwiTWVtb3JhYmxlXCIsIFwiTWVyZVwiLCBcIk1lcnJ5XCIsIFwiTWlnaHR5XCIsXG4gICAgXCJNaXNjaGlldm91c1wiLCBcIk1pc2VyYWJsZVwiLCBcIk1vZGlmaWVkXCIsIFwiTW9vZHlcIiwgXCJNb3N0XCIsIFwiTXlzdGVyaW91c1wiLCBcIk15c3RpY2FsXCIsIFwiTmVlZHlcIixcbiAgICBcIk5lcnZvdXNcIiwgXCJOaWNlXCIsIFwiT2JqZWN0aXZlXCIsIFwiT2Jub3hpb3VzXCIsIFwiT2JzZXNzaXZlXCIsIFwiT2J2aW91c1wiLCBcIk9waW5pb25hdGVkXCIsIFwiT3JhbmdlXCIsXG4gICAgXCJQYWluZnVsXCIsIFwiUGFzc2lvbmF0ZVwiLCBcIlBlcmZlY3RcIiwgXCJQaW5rXCIsIFwiUGxheWZ1bFwiLCBcIlBvaXNvbm91c1wiLCBcIlBvbGl0ZVwiLCBcIlBvb3JcIiwgXCJQb3B1bGFyXCIsIFwiUG93ZXJmdWxcIixcbiAgICBcIlByZWNpc2VcIiwgXCJQcmVzZXJ2ZWRcIiwgXCJQcmV0dHlcIiwgXCJQdXJwbGVcIiwgXCJRdWlja1wiLCBcIlF1aWV0XCIsIFwiUmFuZG9tXCIsIFwiUmFwaWRcIiwgXCJSYXJlXCIsIFwiUmVhbFwiLFxuICAgIFwiUmVhc3N1cmluZ1wiLCBcIlJlY2tsZXNzXCIsIFwiUmVkXCIsIFwiUmVndWxhclwiLCBcIlJlbW9yc2VmdWxcIiwgXCJSZXNwb25zaWJsZVwiLCBcIlJpY2hcIiwgXCJSdWRlXCIsIFwiUnV0aGxlc3NcIixcbiAgICBcIlNhZFwiLCBcIlNjYXJlZFwiLCBcIlNjYXJ5XCIsIFwiU2Nvcm5mdWxcIiwgXCJTY3JlYW1pbmdcIiwgXCJTZWxmaXNoXCIsIFwiU2VyaW91c1wiLCBcIlNoYWR5XCIsIFwiU2hha3lcIiwgXCJTaGFycFwiLFxuICAgIFwiU2hpbnlcIiwgXCJTaHlcIiwgXCJTaW1wbGVcIiwgXCJTbGVlcHlcIiwgXCJTbG93XCIsIFwiU2x5XCIsIFwiU21hbGxcIiwgXCJTbWFydFwiLCBcIlNtZWxseVwiLCBcIlNtaWxpbmdcIiwgXCJTbW9vdGhcIixcbiAgICBcIlNtdWdcIiwgXCJTb2JlclwiLCBcIlNvZnRcIiwgXCJTb2xlbW5cIiwgXCJTcXVhcmVcIiwgXCJTcXVhcmVcIiwgXCJTdGVhZHlcIiwgXCJTdHJhbmdlXCIsIFwiU3Ryb25nXCIsXG4gICAgXCJTdHVubmluZ1wiLCBcIlN1YmplY3RpdmVcIiwgXCJTdWNjZXNzZnVsXCIsIFwiU3VybHlcIiwgXCJTd2VldFwiLCBcIlRhY3RmdWxcIiwgXCJUZW5zZVwiLFxuICAgIFwiVGhvdWdodGZ1bFwiLCBcIlRpZ2h0XCIsIFwiVGlueVwiLCBcIlRvbGVyYW50XCIsIFwiVW5lYXN5XCIsIFwiVW5pcXVlXCIsIFwiVW5zZWVuXCIsIFwiV2FybVwiLCBcIldlYWtcIixcbiAgICBcIldlaXJkXCIsIFwiV2VsbENvb2tlZFwiLCBcIldpbGRcIiwgXCJXaXNlXCIsIFwiV2l0dHlcIiwgXCJXb25kZXJmdWxcIiwgXCJXb3JyaWVkXCIsIFwiWWVsbG93XCIsIFwiWW91bmdcIixcbiAgICBcIlplYWxvdXNcIlxuICAgIF07XG4vL3ZhciBwcm9ub3VucyA9IFtcbi8vXTtcbi8vdmFyIGNvbmp1bmN0aW9ucyA9IFtcbi8vXCJBbmRcIiwgXCJPclwiLCBcIkZvclwiLCBcIkFib3ZlXCIsIFwiQmVmb3JlXCIsIFwiQWdhaW5zdFwiLCBcIkJldHdlZW5cIlxuLy9dO1xuXG4vKlxuICogTWFwcyBhIHN0cmluZyAoY2F0ZWdvcnkgbmFtZSkgdG8gdGhlIGFycmF5IG9mIHdvcmRzIGZyb20gdGhhdCBjYXRlZ29yeS5cbiAqL1xudmFyIENBVEVHT1JJRVMgPVxue1xuICAgIC8vXCJfTk9VTl9cIjogbm91bnMsXG4gICAgXCJfUExVUkFMTk9VTl9cIjogcGx1cmFsTm91bnMsXG4gICAgLy9cIl9QTEFDRV9cIjogcGxhY2VzLFxuICAgIFwiX1ZFUkJfXCI6IHZlcmJzLFxuICAgIFwiX0FEVkVSQl9cIjogYWR2ZXJicyxcbiAgICBcIl9BREpFQ1RJVkVfXCI6IGFkamVjdGl2ZXNcbiAgICAvL1wiX1BST05PVU5fXCI6IHByb25vdW5zLFxuICAgIC8vXCJfQ09OSlVOQ1RJT05fXCI6IGNvbmp1bmN0aW9ucyxcbn07XG5cbnZhciBQQVRURVJOUyA9IFtcbiAgICBcIl9BREpFQ1RJVkVfX1BMVVJBTE5PVU5fX1ZFUkJfX0FEVkVSQl9cIlxuXG4gICAgLy8gQmVhdXRpZnVsRnVuZ2lPclNwYWdoZXR0aVxuICAgIC8vXCJfQURKRUNUSVZFX19QTFVSQUxOT1VOX19DT05KVU5DVElPTl9fUExVUkFMTk9VTl9cIixcblxuICAgIC8vIEFtYXppbmdseVNjYXJ5VG95XG4gICAgLy9cIl9BRFZFUkJfX0FESkVDVElWRV9fTk9VTl9cIixcblxuICAgIC8vIE5laXRoZXJUcmFzaE5vclJpZmxlXG4gICAgLy9cIk5laXRoZXJfTk9VTl9Ob3JfTk9VTl9cIixcbiAgICAvL1wiRWl0aGVyX05PVU5fT3JfTk9VTl9cIixcblxuICAgIC8vIEVpdGhlckNvcHVsYXRlT3JJbnZlc3RpZ2F0ZVxuICAgIC8vXCJFaXRoZXJfVkVSQl9Pcl9WRVJCX1wiLFxuICAgIC8vXCJOZWl0aGVyX1ZFUkJfTm9yX1ZFUkJfXCIsXG5cbiAgICAvL1wiVGhlX0FESkVDVElWRV9fQURKRUNUSVZFX19OT1VOX1wiLFxuICAgIC8vXCJUaGVfQURWRVJCX19BREpFQ1RJVkVfX05PVU5fXCIsXG4gICAgLy9cIlRoZV9BRFZFUkJfX0FESkVDVElWRV9fTk9VTl9zXCIsXG4gICAgLy9cIlRoZV9BRFZFUkJfX0FESkVDVElWRV9fUExVUkFMTk9VTl9fVkVSQl9cIixcblxuICAgIC8vIFdvbHZlc0NvbXB1dGVCYWRseVxuICAgIC8vXCJfUExVUkFMTk9VTl9fVkVSQl9fQURWRVJCX1wiLFxuXG4gICAgLy8gVW5pdGVGYWNpbGl0YXRlQW5kTWVyZ2VcbiAgICAvL1wiX1ZFUkJfX1ZFUkJfQW5kX1ZFUkJfXCIsXG5cbiAgICAvL05hc3R5V2l0Y2hlc0F0VGhlUHViXG4gICAgLy9cIl9BREpFQ1RJVkVfX1BMVVJBTE5PVU5fQXRUaGVfUExBQ0VfXCIsXG5dO1xuXG5cbi8qXG4gKiBSZXR1cm5zIGEgcmFuZG9tIGVsZW1lbnQgZnJvbSB0aGUgYXJyYXkgJ2FycidcbiAqL1xuZnVuY3Rpb24gcmFuZG9tRWxlbWVudChhcnIpXG57XG4gICAgcmV0dXJuIGFycltNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiBhcnIubGVuZ3RoKV07XG59XG5cbi8qXG4gKiBSZXR1cm5zIHRydWUgaWYgdGhlIHN0cmluZyAncycgY29udGFpbnMgb25lIG9mIHRoZVxuICogdGVtcGxhdGUgc3RyaW5ncy5cbiAqL1xuZnVuY3Rpb24gaGFzVGVtcGxhdGUocylcbntcbiAgICBmb3IgKHZhciB0ZW1wbGF0ZSBpbiBDQVRFR09SSUVTKXtcbiAgICAgICAgaWYgKHMuaW5kZXhPZih0ZW1wbGF0ZSkgPj0gMCl7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuLyoqXG4gKiBHZW5lcmF0ZXMgbmV3IHJvb20gbmFtZS5cbiAqL1xudmFyIFJvb21OYW1lR2VuZXJhdG9yID0ge1xuICAgIGdlbmVyYXRlUm9vbVdpdGhvdXRTZXBhcmF0b3I6IGZ1bmN0aW9uKClcbiAgICB7XG4gICAgICAgIC8vIE5vdGUgdGhhdCBpZiBtb3JlIHRoYW4gb25lIHBhdHRlcm4gaXMgYXZhaWxhYmxlLCB0aGUgY2hvaWNlIG9mICduYW1lJyB3b24ndCBiZSByYW5kb20gKG5hbWVzIGZyb20gcGF0dGVybnNcbiAgICAgICAgLy8gd2l0aCBmZXdlciBvcHRpb25zIHdpbGwgaGF2ZSBoaWdoZXIgcHJvYmFiaWxpdHkgb2YgYmVpbmcgY2hvc2VuIHRoYXQgbmFtZXMgZnJvbSBwYXR0ZXJucyB3aXRoIG1vcmUgb3B0aW9ucykuXG4gICAgICAgIHZhciBuYW1lID0gcmFuZG9tRWxlbWVudChQQVRURVJOUyk7XG4gICAgICAgIHZhciB3b3JkO1xuICAgICAgICB3aGlsZSAoaGFzVGVtcGxhdGUobmFtZSkpe1xuICAgICAgICAgICAgZm9yICh2YXIgdGVtcGxhdGUgaW4gQ0FURUdPUklFUyl7XG4gICAgICAgICAgICAgICAgd29yZCA9IHJhbmRvbUVsZW1lbnQoQ0FURUdPUklFU1t0ZW1wbGF0ZV0pO1xuICAgICAgICAgICAgICAgIG5hbWUgPSBuYW1lLnJlcGxhY2UodGVtcGxhdGUsIHdvcmQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG5hbWU7XG4gICAgfVxufVxuXG5tb2R1bGUuZXhwb3J0cyA9IFJvb21OYW1lR2VuZXJhdG9yO1xuIiwidmFyIGFuaW1hdGVUaW1lb3V0LCB1cGRhdGVUaW1lb3V0O1xuXG52YXIgUm9vbU5hbWVHZW5lcmF0b3IgPSByZXF1aXJlKFwiLi9Sb29tbmFtZUdlbmVyYXRvclwiKTtcblxuZnVuY3Rpb24gZW50ZXJfcm9vbSgpXG57XG4gICAgdmFyIHZhbCA9ICQoXCIjZW50ZXJfcm9vbV9maWVsZFwiKS52YWwoKTtcbiAgICBpZighdmFsKSB7XG4gICAgICAgIHZhbCA9ICQoXCIjZW50ZXJfcm9vbV9maWVsZFwiKS5hdHRyKFwicm9vbV9uYW1lXCIpO1xuICAgIH1cbiAgICBpZiAodmFsKSB7XG4gICAgICAgIHdpbmRvdy5sb2NhdGlvbi5wYXRobmFtZSA9IFwiL1wiICsgdmFsO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gYW5pbWF0ZSh3b3JkKSB7XG4gICAgdmFyIGN1cnJlbnRWYWwgPSAkKFwiI2VudGVyX3Jvb21fZmllbGRcIikuYXR0cihcInBsYWNlaG9sZGVyXCIpO1xuICAgICQoXCIjZW50ZXJfcm9vbV9maWVsZFwiKS5hdHRyKFwicGxhY2Vob2xkZXJcIiwgY3VycmVudFZhbCArIHdvcmQuc3Vic3RyKDAsIDEpKTtcbiAgICBhbmltYXRlVGltZW91dCA9IHNldFRpbWVvdXQoZnVuY3Rpb24oKSB7XG4gICAgICAgIGFuaW1hdGUod29yZC5zdWJzdHJpbmcoMSwgd29yZC5sZW5ndGgpKVxuICAgIH0sIDcwKTtcbn1cblxuZnVuY3Rpb24gdXBkYXRlX3Jvb21uYW1lKClcbntcbiAgICB2YXIgd29yZCA9IFJvb21OYW1lR2VuZXJhdG9yLmdlbmVyYXRlUm9vbVdpdGhvdXRTZXBhcmF0b3IoKTtcbiAgICAkKFwiI2VudGVyX3Jvb21fZmllbGRcIikuYXR0cihcInJvb21fbmFtZVwiLCB3b3JkKTtcbiAgICAkKFwiI2VudGVyX3Jvb21fZmllbGRcIikuYXR0cihcInBsYWNlaG9sZGVyXCIsIFwiXCIpO1xuICAgIGNsZWFyVGltZW91dChhbmltYXRlVGltZW91dCk7XG4gICAgYW5pbWF0ZSh3b3JkKTtcbiAgICB1cGRhdGVUaW1lb3V0ID0gc2V0VGltZW91dCh1cGRhdGVfcm9vbW5hbWUsIDEwMDAwKTtcbn1cblxuXG5mdW5jdGlvbiBzZXR1cFdlbGNvbWVQYWdlKClcbntcbiAgICAkKFwiI3ZpZGVvY29uZmVyZW5jZV9wYWdlXCIpLmhpZGUoKTtcbiAgICAkKFwiI2RvbWFpbl9uYW1lXCIpLnRleHQoXG4gICAgICAgICAgICB3aW5kb3cubG9jYXRpb24ucHJvdG9jb2wgKyBcIi8vXCIgKyB3aW5kb3cubG9jYXRpb24uaG9zdCArIFwiL1wiKTtcbiAgICAkKFwic3BhbltuYW1lPSdhcHBOYW1lJ11cIikudGV4dChpbnRlcmZhY2VDb25maWcuQVBQX05BTUUpO1xuXG4gICAgaWYgKGludGVyZmFjZUNvbmZpZy5TSE9XX0pJVFNJX1dBVEVSTUFSSykge1xuICAgICAgICB2YXIgbGVmdFdhdGVybWFya0RpdlxuICAgICAgICAgICAgPSAkKFwiI3dlbGNvbWVfcGFnZV9oZWFkZXIgZGl2W2NsYXNzPSd3YXRlcm1hcmsgbGVmdHdhdGVybWFyayddXCIpO1xuICAgICAgICBpZihsZWZ0V2F0ZXJtYXJrRGl2ICYmIGxlZnRXYXRlcm1hcmtEaXYubGVuZ3RoID4gMClcbiAgICAgICAge1xuICAgICAgICAgICAgbGVmdFdhdGVybWFya0Rpdi5jc3Moe2Rpc3BsYXk6ICdibG9jayd9KTtcbiAgICAgICAgICAgIGxlZnRXYXRlcm1hcmtEaXYucGFyZW50KCkuZ2V0KDApLmhyZWZcbiAgICAgICAgICAgICAgICA9IGludGVyZmFjZUNvbmZpZy5KSVRTSV9XQVRFUk1BUktfTElOSztcbiAgICAgICAgfVxuXG4gICAgfVxuXG4gICAgaWYgKGludGVyZmFjZUNvbmZpZy5TSE9XX0JSQU5EX1dBVEVSTUFSSykge1xuICAgICAgICB2YXIgcmlnaHRXYXRlcm1hcmtEaXZcbiAgICAgICAgICAgID0gJChcIiN3ZWxjb21lX3BhZ2VfaGVhZGVyIGRpdltjbGFzcz0nd2F0ZXJtYXJrIHJpZ2h0d2F0ZXJtYXJrJ11cIik7XG4gICAgICAgIGlmKHJpZ2h0V2F0ZXJtYXJrRGl2ICYmIHJpZ2h0V2F0ZXJtYXJrRGl2Lmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIHJpZ2h0V2F0ZXJtYXJrRGl2LmNzcyh7ZGlzcGxheTogJ2Jsb2NrJ30pO1xuICAgICAgICAgICAgcmlnaHRXYXRlcm1hcmtEaXYucGFyZW50KCkuZ2V0KDApLmhyZWZcbiAgICAgICAgICAgICAgICA9IGludGVyZmFjZUNvbmZpZy5CUkFORF9XQVRFUk1BUktfTElOSztcbiAgICAgICAgICAgIHJpZ2h0V2F0ZXJtYXJrRGl2LmdldCgwKS5zdHlsZS5iYWNrZ3JvdW5kSW1hZ2VcbiAgICAgICAgICAgICAgICA9IFwidXJsKGltYWdlcy9yaWdodHdhdGVybWFyay5wbmcpXCI7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoaW50ZXJmYWNlQ29uZmlnLlNIT1dfUE9XRVJFRF9CWSkge1xuICAgICAgICAkKFwiI3dlbGNvbWVfcGFnZV9oZWFkZXI+YVtjbGFzcz0ncG93ZXJlZGJ5J11cIilcbiAgICAgICAgICAgIC5jc3Moe2Rpc3BsYXk6ICdibG9jayd9KTtcbiAgICB9XG5cbiAgICAkKFwiI2VudGVyX3Jvb21fYnV0dG9uXCIpLmNsaWNrKGZ1bmN0aW9uKClcbiAgICB7XG4gICAgICAgIGVudGVyX3Jvb20oKTtcbiAgICB9KTtcblxuICAgICQoXCIjZW50ZXJfcm9vbV9maWVsZFwiKS5rZXlkb3duKGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBpZiAoZXZlbnQua2V5Q29kZSA9PT0gMTMgLyogZW50ZXIgKi8pIHtcbiAgICAgICAgICAgIGVudGVyX3Jvb20oKTtcbiAgICAgICAgfVxuICAgIH0pO1xuXG4gICAgaWYgKCEoaW50ZXJmYWNlQ29uZmlnLkdFTkVSQVRFX1JPT01OQU1FU19PTl9XRUxDT01FX1BBR0UgPT09IGZhbHNlKSl7XG4gICAgICAgIHZhciB1cGRhdGVUaW1lb3V0O1xuICAgICAgICB2YXIgYW5pbWF0ZVRpbWVvdXQ7XG4gICAgICAgICQoXCIjcmVsb2FkX3Jvb21uYW1lXCIpLmNsaWNrKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGNsZWFyVGltZW91dCh1cGRhdGVUaW1lb3V0KTtcbiAgICAgICAgICAgIGNsZWFyVGltZW91dChhbmltYXRlVGltZW91dCk7XG4gICAgICAgICAgICB1cGRhdGVfcm9vbW5hbWUoKTtcbiAgICAgICAgfSk7XG4gICAgICAgICQoXCIjcmVsb2FkX3Jvb21uYW1lXCIpLnNob3coKTtcblxuXG4gICAgICAgIHVwZGF0ZV9yb29tbmFtZSgpO1xuICAgIH1cblxuICAgICQoXCIjZGlzYWJsZV93ZWxjb21lXCIpLmNsaWNrKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgd2luZG93LmxvY2FsU3RvcmFnZS53ZWxjb21lUGFnZURpc2FibGVkXG4gICAgICAgICAgICA9ICQoXCIjZGlzYWJsZV93ZWxjb21lXCIpLmlzKFwiOmNoZWNrZWRcIik7XG4gICAgfSk7XG5cbn1cblxubW9kdWxlLmV4cG9ydHMgPSBzZXR1cFdlbGNvbWVQYWdlOyJdfQ== diff --git a/libs/modules/connectionquality.bundle.js b/libs/modules/connectionquality.bundle.js index 50b92a66d..ee43400f6 100644 --- a/libs/modules/connectionquality.bundle.js +++ b/libs/modules/connectionquality.bundle.js @@ -30,8 +30,7 @@ function startSendingStats() { * Sends statistics to other participants */ function sendStats() { - connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats)); - connection.emuc.sendPresence(); + xmpp.addToPresence("connectionQuality", convertToMUCStats(stats)); } /** @@ -119,4 +118,5 @@ var ConnectionQuality = { module.exports = ConnectionQuality; },{}]},{},[1])(1) -}); \ No newline at end of file +}); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi91c3IvbG9jYWwvbGliL25vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL2Nvbm5lY3Rpb25xdWFsaXR5L2Nvbm5lY3Rpb25xdWFsaXR5LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiLyoqXG4gKiBsb2NhbCBzdGF0c1xuICogQHR5cGUge3t9fVxuICovXG52YXIgc3RhdHMgPSB7fTtcblxuLyoqXG4gKiByZW1vdGUgc3RhdHNcbiAqIEB0eXBlIHt7fX1cbiAqL1xudmFyIHJlbW90ZVN0YXRzID0ge307XG5cbi8qKlxuICogSW50ZXJ2YWwgZm9yIHNlbmRpbmcgc3RhdGlzdGljcyB0byBvdGhlciBwYXJ0aWNpcGFudHNcbiAqIEB0eXBlIHtudWxsfVxuICovXG52YXIgc2VuZEludGVydmFsSWQgPSBudWxsO1xuXG5cbi8qKlxuICogU3RhcnQgc3RhdGlzdGljcyBzZW5kaW5nLlxuICovXG5mdW5jdGlvbiBzdGFydFNlbmRpbmdTdGF0cygpIHtcbiAgICBzZW5kU3RhdHMoKTtcbiAgICBzZW5kSW50ZXJ2YWxJZCA9IHNldEludGVydmFsKHNlbmRTdGF0cywgMTAwMDApO1xufVxuXG4vKipcbiAqIFNlbmRzIHN0YXRpc3RpY3MgdG8gb3RoZXIgcGFydGljaXBhbnRzXG4gKi9cbmZ1bmN0aW9uIHNlbmRTdGF0cygpIHtcbiAgICB4bXBwLmFkZFRvUHJlc2VuY2UoXCJjb25uZWN0aW9uUXVhbGl0eVwiLCBjb252ZXJ0VG9NVUNTdGF0cyhzdGF0cykpO1xufVxuXG4vKipcbiAqIENvbnZlcnRzIHN0YXRpc3RpY3MgdG8gZm9ybWF0IGZvciBzZW5kaW5nIHRocm91Z2ggWE1QUFxuICogQHBhcmFtIHN0YXRzIHRoZSBzdGF0aXN0aWNzXG4gKiBAcmV0dXJucyB7e2JpdHJhdGVfZG9ud2xvYWQ6ICosIGJpdHJhdGVfdXBscG9hZDogKiwgcGFja2V0TG9zc190b3RhbDogKiwgcGFja2V0TG9zc19kb3dubG9hZDogKiwgcGFja2V0TG9zc191cGxvYWQ6ICp9fVxuICovXG5mdW5jdGlvbiBjb252ZXJ0VG9NVUNTdGF0cyhzdGF0cykge1xuICAgIHJldHVybiB7XG4gICAgICAgIFwiYml0cmF0ZV9kb3dubG9hZFwiOiBzdGF0cy5iaXRyYXRlLmRvd25sb2FkLFxuICAgICAgICBcImJpdHJhdGVfdXBsb2FkXCI6IHN0YXRzLmJpdHJhdGUudXBsb2FkLFxuICAgICAgICBcInBhY2tldExvc3NfdG90YWxcIjogc3RhdHMucGFja2V0TG9zcy50b3RhbCxcbiAgICAgICAgXCJwYWNrZXRMb3NzX2Rvd25sb2FkXCI6IHN0YXRzLnBhY2tldExvc3MuZG93bmxvYWQsXG4gICAgICAgIFwicGFja2V0TG9zc191cGxvYWRcIjogc3RhdHMucGFja2V0TG9zcy51cGxvYWRcbiAgICB9O1xufVxuXG4vKipcbiAqIENvbnZlcnRzIHN0YXRpdGlzdGljcyB0byBmb3JtYXQgdXNlZCBieSBWaWRlb0xheW91dFxuICogQHBhcmFtIHN0YXRzXG4gKiBAcmV0dXJucyB7e2JpdHJhdGU6IHtkb3dubG9hZDogKiwgdXBsb2FkOiAqfSwgcGFja2V0TG9zczoge3RvdGFsOiAqLCBkb3dubG9hZDogKiwgdXBsb2FkOiAqfX19XG4gKi9cbmZ1bmN0aW9uIHBhcnNlTVVDU3RhdHMoc3RhdHMpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICBiaXRyYXRlOiB7XG4gICAgICAgICAgICBkb3dubG9hZDogc3RhdHMuYml0cmF0ZV9kb3dubG9hZCxcbiAgICAgICAgICAgIHVwbG9hZDogc3RhdHMuYml0cmF0ZV91cGxvYWRcbiAgICAgICAgfSxcbiAgICAgICAgcGFja2V0TG9zczoge1xuICAgICAgICAgICAgdG90YWw6IHN0YXRzLnBhY2tldExvc3NfdG90YWwsXG4gICAgICAgICAgICBkb3dubG9hZDogc3RhdHMucGFja2V0TG9zc19kb3dubG9hZCxcbiAgICAgICAgICAgIHVwbG9hZDogc3RhdHMucGFja2V0TG9zc191cGxvYWRcbiAgICAgICAgfVxuICAgIH07XG59XG5cblxudmFyIENvbm5lY3Rpb25RdWFsaXR5ID0ge1xuICAgIC8qKlxuICAgICAqIFVwZGF0ZXMgdGhlIGxvY2FsIHN0YXRpc3RpY3NcbiAgICAgKiBAcGFyYW0gZGF0YSBuZXcgc3RhdGlzdGljc1xuICAgICAqL1xuICAgIHVwZGF0ZUxvY2FsU3RhdHM6IGZ1bmN0aW9uIChkYXRhKSB7XG4gICAgICAgIHN0YXRzID0gZGF0YTtcbiAgICAgICAgVUkudXBkYXRlTG9jYWxDb25uZWN0aW9uU3RhdHMoMTAwIC0gc3RhdHMucGFja2V0TG9zcy50b3RhbCwgc3RhdHMpO1xuICAgICAgICBpZiAoc2VuZEludGVydmFsSWQgPT0gbnVsbCkge1xuICAgICAgICAgICAgc3RhcnRTZW5kaW5nU3RhdHMoKTtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBVcGRhdGVzIHJlbW90ZSBzdGF0aXN0aWNzXG4gICAgICogQHBhcmFtIGppZCB0aGUgamlkIGFzc29jaWF0ZWQgd2l0aCB0aGUgc3RhdGlzdGljc1xuICAgICAqIEBwYXJhbSBkYXRhIHRoZSBzdGF0aXN0aWNzXG4gICAgICovXG4gICAgdXBkYXRlUmVtb3RlU3RhdHM6IGZ1bmN0aW9uIChqaWQsIGRhdGEpIHtcbiAgICAgICAgaWYgKGRhdGEgPT0gbnVsbCB8fCBkYXRhLnBhY2tldExvc3NfdG90YWwgPT0gbnVsbCkge1xuICAgICAgICAgICAgVUkudXBkYXRlQ29ubmVjdGlvblN0YXRzKGppZCwgbnVsbCwgbnVsbCk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgcmVtb3RlU3RhdHNbamlkXSA9IHBhcnNlTVVDU3RhdHMoZGF0YSk7XG5cbiAgICAgICAgVUkudXBkYXRlQ29ubmVjdGlvblN0YXRzKGppZCwgMTAwIC0gZGF0YS5wYWNrZXRMb3NzX3RvdGFsLCByZW1vdGVTdGF0c1tqaWRdKTtcblxuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBTdG9wcyBzdGF0aXN0aWNzIHNlbmRpbmcuXG4gICAgICovXG4gICAgc3RvcFNlbmRpbmdTdGF0czogZnVuY3Rpb24gKCkge1xuICAgICAgICBjbGVhckludGVydmFsKHNlbmRJbnRlcnZhbElkKTtcbiAgICAgICAgc2VuZEludGVydmFsSWQgPSBudWxsO1xuICAgICAgICAvL25vdGlmeSBVSSBhYm91dCBzdG9wcGluZyBzdGF0aXN0aWNzIGdhdGhlcmluZ1xuICAgICAgICBVSS5vblN0YXRzU3RvcCgpO1xuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBsb2NhbCBzdGF0aXN0aWNzLlxuICAgICAqL1xuICAgIGdldFN0YXRzOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBzdGF0cztcbiAgICB9XG5cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gQ29ubmVjdGlvblF1YWxpdHk7Il19 diff --git a/libs/modules/desktopsharing.bundle.js b/libs/modules/desktopsharing.bundle.js index 3ce0d817c..dc8e493b5 100644 --- a/libs/modules/desktopsharing.bundle.js +++ b/libs/modules/desktopsharing.bundle.js @@ -1,5 +1,5 @@ !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.desktopsharing=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { + var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session); + ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1'; + cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder', + name: (cands[0].sdpMid? cands[0].sdpMid : mline.media) + }).c('transport', ice); + for (var i = 0; i < cands.length; i++) { + cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up(); + } + // add fingerprint + if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) { + var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)); + tmp.required = true; + cand.c( + 'fingerprint', + {xmlns: 'urn:xmpp:jingle:apps:dtls:0'}) + .t(tmp.fingerprint); + delete tmp.fingerprint; + cand.attrs(tmp); + cand.up(); + } + cand.up(); // transport + cand.up(); // content + } + } + // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340 + //console.log('was this the last candidate', this.lasticecandidate); + this.connection.sendIQ(cand, + function () { + var ack = {}; + ack.source = 'transportinfo'; + $(document).trigger('ack.jingle', [this.sid, ack]); + }, + function (stanza) { + var error = ($(stanza).find('error').length) ? { + code: $(stanza).find('error').attr('code'), + reason: $(stanza).find('error :first')[0].tagName, + }:{}; + error.source = 'transportinfo'; + JingleSession.onJingleError(this.sid, error); + }, + 10000); +}; + + +JingleSession.prototype.sendOffer = function () { + //console.log('sendOffer...'); + var self = this; + this.peerconnection.createOffer(function (sdp) { + self.createdOffer(sdp); + }, + function (e) { + console.error('createOffer failed', e); + }, + this.media_constraints + ); +}; + +JingleSession.prototype.createdOffer = function (sdp) { + //console.log('createdOffer', sdp); + var self = this; + this.localSDP = new SDP(sdp.sdp); + //this.localSDP.mangle(); + var sendJingle = function () { + var init = $iq({to: this.peerjid, + type: 'set'}) + .c('jingle', {xmlns: 'urn:xmpp:jingle:1', + action: 'session-initiate', + initiator: this.initiator, + sid: this.sid}); + self.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC); + self.connection.sendIQ(init, + function () { + var ack = {}; + ack.source = 'offer'; + $(document).trigger('ack.jingle', [self.sid, ack]); + }, + function (stanza) { + self.state = 'error'; + self.peerconnection.close(); + var error = ($(stanza).find('error').length) ? { + code: $(stanza).find('error').attr('code'), + reason: $(stanza).find('error :first')[0].tagName, + }:{}; + error.source = 'offer'; + JingleSession.onJingleError(self.sid, error); + }, + 10000); + } + sdp.sdp = this.localSDP.raw; + this.peerconnection.setLocalDescription(sdp, + function () { + if(self.usetrickle) + { + sendJingle(); + } + self.setLocalDescription(); + //console.log('setLocalDescription success'); + }, + function (e) { + console.error('setLocalDescription failed', e); + } + ); + var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:'); + for (var i = 0; i < cands.length; i++) { + var cand = SDPUtil.parse_icecandidate(cands[i]); + if (cand.type == 'srflx') { + this.hadstuncandidate = true; + } else if (cand.type == 'relay') { + this.hadturncandidate = true; + } + } +}; + +JingleSession.prototype.setRemoteDescription = function (elem, desctype) { + //console.log('setting remote description... ', desctype); + this.remoteSDP = new SDP(''); + this.remoteSDP.fromJingle(elem); + if (this.peerconnection.remoteDescription !== null) { + console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription); + if (this.peerconnection.remoteDescription.type == 'pranswer') { + var pranswer = new SDP(this.peerconnection.remoteDescription.sdp); + for (var i = 0; i < pranswer.media.length; i++) { + // make sure we have ice ufrag and pwd + if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) { + if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) { + this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n'; + } else { + console.warn('no ice ufrag?'); + } + if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) { + this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n'; + } else { + console.warn('no ice pwd?'); + } + } + // copy over candidates + var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:'); + for (var j = 0; j < lines.length; j++) { + this.remoteSDP.media[i] += lines[j] + '\r\n'; + } + } + this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join(''); + } + } + var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw}); + + this.peerconnection.setRemoteDescription(remotedesc, + function () { + //console.log('setRemoteDescription success'); + }, + function (e) { + console.error('setRemoteDescription error', e); + JingleSession.onJingleFatalError(self, e); + } + ); +}; + +JingleSession.prototype.addIceCandidate = function (elem) { + var self = this; + if (this.peerconnection.signalingState == 'closed') { + return; + } + if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') { + console.log('trickle ice candidate arriving before session accept...'); + // create a PRANSWER for setRemoteDescription + if (!this.remoteSDP) { + var cobbled = 'v=0\r\n' + + 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME + 's=-\r\n' + + 't=0 0\r\n'; + // first, take some things from the local description + for (var i = 0; i < this.localSDP.media.length; i++) { + cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n'; + cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n'; + if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) { + cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n'; + } + cobbled += 'a=inactive\r\n'; + } + this.remoteSDP = new SDP(cobbled); + } + // then add things like ice and dtls from remote candidate + elem.each(function () { + for (var i = 0; i < self.remoteSDP.media.length; i++) { + if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) || + self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) { + if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) { + var tmp = $(this).find('transport'); + self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n'; + self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n'; + tmp = $(this).find('transport>fingerprint'); + if (tmp.length) { + self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n'; + } else { + console.log('no dtls fingerprint (webrtc issue #1718?)'); + self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n'; + } + break; + } + } + } + }); + this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join(''); + + // we need a complete SDP with ice-ufrag/ice-pwd in all parts + // this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts + // but it could be in the session part as well. since the code above constructs this sdp this can't happen however + var iscomplete = this.remoteSDP.media.filter(function (mediapart) { + return SDPUtil.find_line(mediapart, 'a=ice-ufrag:'); + }).length == this.remoteSDP.media.length; + + if (iscomplete) { + console.log('setting pranswer'); + try { + this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }), + function() { + }, + function(e) { + console.log('setRemoteDescription pranswer failed', e.toString()); + }); + } catch (e) { + console.error('setting pranswer failed', e); + } + } else { + //console.log('not yet setting pranswer'); + } + } + // operate on each content element + elem.each(function () { + // would love to deactivate this, but firefox still requires it + var idx = -1; + var i; + for (i = 0; i < self.remoteSDP.media.length; i++) { + if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) || + self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) { + idx = i; + break; + } + } + if (idx == -1) { // fall back to localdescription + for (i = 0; i < self.localSDP.media.length; i++) { + if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) || + self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) { + idx = i; + break; + } + } + } + var name = $(this).attr('name'); + // TODO: check ice-pwd and ice-ufrag? + $(this).find('transport>candidate').each(function () { + var line, candidate; + line = SDPUtil.candidateFromJingle(this); + candidate = new RTCIceCandidate({sdpMLineIndex: idx, + sdpMid: name, + candidate: line}); + try { + self.peerconnection.addIceCandidate(candidate); + } catch (e) { + console.error('addIceCandidate failed', e.toString(), line); + } + }); + }); +}; + +JingleSession.prototype.sendAnswer = function (provisional) { + //console.log('createAnswer', provisional); + var self = this; + this.peerconnection.createAnswer( + function (sdp) { + self.createdAnswer(sdp, provisional); + }, + function (e) { + console.error('createAnswer failed', e); + }, + this.media_constraints + ); +}; + +JingleSession.prototype.createdAnswer = function (sdp, provisional) { + //console.log('createAnswer callback'); + var self = this; + this.localSDP = new SDP(sdp.sdp); + //this.localSDP.mangle(); + this.usepranswer = provisional === true; + if (this.usetrickle) { + if (this.usepranswer) { + sdp.type = 'pranswer'; + for (var i = 0; i < this.localSDP.media.length; i++) { + this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n'); + } + this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join(''); + } + } + var self = this; + var sendJingle = function (ssrcs) { + + var accept = $iq({to: self.peerjid, + type: 'set'}) + .c('jingle', {xmlns: 'urn:xmpp:jingle:1', + action: 'session-accept', + initiator: self.initiator, + responder: self.responder, + sid: self.sid }); + var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp); + var publicLocalSDP = new SDP(publicLocalDesc.sdp); + publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs); + self.connection.sendIQ(accept, + function () { + var ack = {}; + ack.source = 'answer'; + $(document).trigger('ack.jingle', [self.sid, ack]); + }, + function (stanza) { + var error = ($(stanza).find('error').length) ? { + code: $(stanza).find('error').attr('code'), + reason: $(stanza).find('error :first')[0].tagName, + }:{}; + error.source = 'answer'; + JingleSession.onJingleError(self.sid, error); + }, + 10000); + } + sdp.sdp = this.localSDP.raw; + this.peerconnection.setLocalDescription(sdp, + function () { + + //console.log('setLocalDescription success'); + if (self.usetrickle && !self.usepranswer) { + sendJingle(); + } + self.setLocalDescription(); + }, + function (e) { + console.error('setLocalDescription failed', e); + } + ); + var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:'); + for (var j = 0; j < cands.length; j++) { + var cand = SDPUtil.parse_icecandidate(cands[j]); + if (cand.type == 'srflx') { + this.hadstuncandidate = true; + } else if (cand.type == 'relay') { + this.hadturncandidate = true; + } + } +}; + +JingleSession.prototype.sendTerminate = function (reason, text) { + var self = this, + term = $iq({to: this.peerjid, + type: 'set'}) + .c('jingle', {xmlns: 'urn:xmpp:jingle:1', + action: 'session-terminate', + initiator: this.initiator, + sid: this.sid}) + .c('reason') + .c(reason || 'success'); + + if (text) { + term.up().c('text').t(text); + } + + this.connection.sendIQ(term, + function () { + self.peerconnection.close(); + self.peerconnection = null; + self.terminate(); + var ack = {}; + ack.source = 'terminate'; + $(document).trigger('ack.jingle', [self.sid, ack]); + }, + function (stanza) { + var error = ($(stanza).find('error').length) ? { + code: $(stanza).find('error').attr('code'), + reason: $(stanza).find('error :first')[0].tagName, + }:{}; + $(document).trigger('ack.jingle', [self.sid, error]); + }, + 10000); + if (this.statsinterval !== null) { + window.clearInterval(this.statsinterval); + this.statsinterval = null; + } +}; + +JingleSession.prototype.addSource = function (elem, fromJid) { + + var self = this; + // FIXME: dirty waiting + if (!this.peerconnection.localDescription) + { + console.warn("addSource - localDescription not ready yet") + setTimeout(function() + { + self.addSource(elem, fromJid); + }, + 200 + ); + return; + } + + console.log('addssrc', new Date().getTime()); + console.log('ice', this.peerconnection.iceConnectionState); + var sdp = new SDP(this.peerconnection.remoteDescription.sdp); + var mySdp = new SDP(this.peerconnection.localDescription.sdp); + + $(elem).each(function (idx, content) { + var name = $(content).attr('name'); + var lines = ''; + tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() { + var semantics = this.getAttribute('semantics'); + var ssrcs = $(this).find('>source').map(function () { + return this.getAttribute('ssrc'); + }).get(); + + if (ssrcs.length != 0) { + lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n'; + } + }); + tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source + tmp.each(function () { + var ssrc = $(this).attr('ssrc'); + if(mySdp.containsSSRC(ssrc)){ + /** + * This happens when multiple participants change their streams at the same time and + * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple + * addssrc are scheduled for update IQ. See + */ + console.warn("Got add stream request for my own ssrc: "+ssrc); + return; + } + $(this).find('>parameter').each(function () { + lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name'); + if ($(this).attr('value') && $(this).attr('value').length) + lines += ':' + $(this).attr('value'); + lines += '\r\n'; + }); + }); + sdp.media.forEach(function(media, idx) { + if (!SDPUtil.find_line(media, 'a=mid:' + name)) + return; + sdp.media[idx] += lines; + if (!self.addssrc[idx]) self.addssrc[idx] = ''; + self.addssrc[idx] += lines; + }); + sdp.raw = sdp.session + sdp.media.join(''); + }); + this.modifySources(); +}; + +JingleSession.prototype.removeSource = function (elem, fromJid) { + + var self = this; + // FIXME: dirty waiting + if (!this.peerconnection.localDescription) + { + console.warn("removeSource - localDescription not ready yet") + setTimeout(function() + { + self.removeSource(elem, fromJid); + }, + 200 + ); + return; + } + + console.log('removessrc', new Date().getTime()); + console.log('ice', this.peerconnection.iceConnectionState); + var sdp = new SDP(this.peerconnection.remoteDescription.sdp); + var mySdp = new SDP(this.peerconnection.localDescription.sdp); + + $(elem).each(function (idx, content) { + var name = $(content).attr('name'); + var lines = ''; + tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() { + var semantics = this.getAttribute('semantics'); + var ssrcs = $(this).find('>source').map(function () { + return this.getAttribute('ssrc'); + }).get(); + + if (ssrcs.length != 0) { + lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n'; + } + }); + tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source + tmp.each(function () { + var ssrc = $(this).attr('ssrc'); + // This should never happen, but can be useful for bug detection + if(mySdp.containsSSRC(ssrc)){ + console.error("Got remove stream request for my own ssrc: "+ssrc); + return; + } + $(this).find('>parameter').each(function () { + lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name'); + if ($(this).attr('value') && $(this).attr('value').length) + lines += ':' + $(this).attr('value'); + lines += '\r\n'; + }); + }); + sdp.media.forEach(function(media, idx) { + if (!SDPUtil.find_line(media, 'a=mid:' + name)) + return; + sdp.media[idx] += lines; + if (!self.removessrc[idx]) self.removessrc[idx] = ''; + self.removessrc[idx] += lines; + }); + sdp.raw = sdp.session + sdp.media.join(''); + }); + this.modifySources(); +}; + +JingleSession.prototype.modifySources = function (successCallback) { + var self = this; + if (this.peerconnection.signalingState == 'closed') return; + if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){ + // There is nothing to do since scheduled job might have been executed by another succeeding call + this.setLocalDescription(); + if(successCallback){ + successCallback(); + } + return; + } + + // FIXME: this is a big hack + // https://code.google.com/p/webrtc/issues/detail?id=2688 + // ^ has been fixed. + if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')) { + console.warn('modifySources not yet', this.peerconnection.signalingState, this.peerconnection.iceConnectionState); + this.wait = true; + window.setTimeout(function() { self.modifySources(successCallback); }, 250); + return; + } + if (this.wait) { + window.setTimeout(function() { self.modifySources(successCallback); }, 2500); + this.wait = false; + return; + } + + // Reset switch streams flag + this.switchstreams = false; + + var sdp = new SDP(this.peerconnection.remoteDescription.sdp); + + // add sources + this.addssrc.forEach(function(lines, idx) { + sdp.media[idx] += lines; + }); + this.addssrc = []; + + // remove sources + this.removessrc.forEach(function(lines, idx) { + lines = lines.split('\r\n'); + lines.pop(); // remove empty last element; + lines.forEach(function(line) { + sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', ''); + }); + }); + this.removessrc = []; + + // FIXME: + // this was a hack for the situation when only one peer exists + // in the conference. + // check if still required and remove + if (sdp.media[0]) + sdp.media[0] = sdp.media[0].replace('a=recvonly', 'a=sendrecv'); + if (sdp.media[1]) + sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv'); + + sdp.raw = sdp.session + sdp.media.join(''); + this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}), + function() { + + if(self.signalingState == 'closed') { + console.error("createAnswer attempt on closed state"); + return; + } + + self.peerconnection.createAnswer( + function(modifiedAnswer) { + // change video direction, see https://github.com/jitsi/jitmeet/issues/41 + if (self.pendingop !== null) { + var sdp = new SDP(modifiedAnswer.sdp); + if (sdp.media.length > 1) { + switch(self.pendingop) { + case 'mute': + sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly'); + break; + case 'unmute': + sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv'); + break; + } + sdp.raw = sdp.session + sdp.media.join(''); + modifiedAnswer.sdp = sdp.raw; + } + self.pendingop = null; + } + + // FIXME: pushing down an answer while ice connection state + // is still checking is bad... + //console.log(self.peerconnection.iceConnectionState); + + // trying to work around another chrome bug + //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass'); + self.peerconnection.setLocalDescription(modifiedAnswer, + function() { + //console.log('modified setLocalDescription ok'); + self.setLocalDescription(); + if(successCallback){ + successCallback(); + } + }, + function(error) { + console.error('modified setLocalDescription failed', error); + } + ); + }, + function(error) { + console.error('modified answer failed', error); + } + ); + }, + function(error) { + console.error('modify failed', error); + } + ); +}; + +/** + * Switches video streams. + * @param new_stream new stream that will be used as video of this session. + * @param oldStream old video stream of this session. + * @param success_callback callback executed after successful stream switch. + */ +JingleSession.prototype.switchStreams = function (new_stream, oldStream, success_callback) { + + var self = this; + + // Remember SDP to figure out added/removed SSRCs + var oldSdp = null; + if(self.peerconnection) { + if(self.peerconnection.localDescription) { + oldSdp = new SDP(self.peerconnection.localDescription.sdp); + } + self.peerconnection.removeStream(oldStream, true); + self.peerconnection.addStream(new_stream); + } + + RTC.switchVideoStreams(new_stream, oldStream); + + // Conference is not active + if(!oldSdp || !self.peerconnection) { + success_callback(); + return; + } + + self.switchstreams = true; + self.modifySources(function() { + console.log('modify sources done'); + + success_callback(); + + var newSdp = new SDP(self.peerconnection.localDescription.sdp); + console.log("SDPs", oldSdp, newSdp); + self.notifyMySSRCUpdate(oldSdp, newSdp); + }); +}; + +/** + * Figures out added/removed ssrcs and send update IQs. + * @param old_sdp SDP object for old description. + * @param new_sdp SDP object for new description. + */ +JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) { + + if (!(this.peerconnection.signalingState == 'stable' && + this.peerconnection.iceConnectionState == 'connected')){ + console.log("Too early to send updates"); + return; + } + + // send source-remove IQ. + sdpDiffer = new SDPDiffer(new_sdp, old_sdp); + var remove = $iq({to: this.peerjid, type: 'set'}) + .c('jingle', { + xmlns: 'urn:xmpp:jingle:1', + action: 'source-remove', + initiator: this.initiator, + sid: this.sid + } + ); + var removed = sdpDiffer.toJingle(remove); + if (removed) { + this.connection.sendIQ(remove, + function (res) { + console.info('got remove result', res); + }, + function (err) { + console.error('got remove error', err); + } + ); + } else { + console.log('removal not necessary'); + } + + // send source-add IQ. + var sdpDiffer = new SDPDiffer(old_sdp, new_sdp); + var add = $iq({to: this.peerjid, type: 'set'}) + .c('jingle', { + xmlns: 'urn:xmpp:jingle:1', + action: 'source-add', + initiator: this.initiator, + sid: this.sid + } + ); + var added = sdpDiffer.toJingle(add); + if (added) { + this.connection.sendIQ(add, + function (res) { + console.info('got add result', res); + }, + function (err) { + console.error('got add error', err); + } + ); + } else { + console.log('addition not necessary'); + } +}; + +/** + * Determines whether the (local) video is mute i.e. all video tracks are + * disabled. + * + * @return true if the (local) video is mute i.e. all video tracks are + * disabled; otherwise, false + */ +JingleSession.prototype.isVideoMute = function () { + var tracks = RTC.localVideo.getVideoTracks(); + var mute = true; + + for (var i = 0; i < tracks.length; ++i) { + if (tracks[i].enabled) { + mute = false; + break; + } + } + return mute; +}; + +/** + * Mutes/unmutes the (local) video i.e. enables/disables all video tracks. + * + * @param mute true to mute the (local) video i.e. to disable all video + * tracks; otherwise, false + * @param callback a function to be invoked with mute after all video + * tracks have been enabled/disabled. The function may, optionally, return + * another function which is to be invoked after the whole mute/unmute operation + * has completed successfully. + * @param options an object which specifies optional arguments such as the + * boolean key byUser with default value true which + * specifies whether the method was initiated in response to a user command (in + * contrast to an automatic decision made by the application logic) + */ +JingleSession.prototype.setVideoMute = function (mute, callback, options) { + var byUser; + + if (options) { + byUser = options.byUser; + if (typeof byUser === 'undefined') { + byUser = true; + } + } else { + byUser = true; + } + // The user's command to mute the (local) video takes precedence over any + // automatic decision made by the application logic. + if (byUser) { + this.videoMuteByUser = mute; + } else if (this.videoMuteByUser) { + return; + } + + var self = this; + var localCallback = function (mute) { + self.connection.emuc.addVideoInfoToPresence(mute); + self.connection.emuc.sendPresence(); + return callback(mute) + }; + + if (mute == RTC.localVideo.isMuted()) + { + // Even if no change occurs, the specified callback is to be executed. + // The specified callback may, optionally, return a successCallback + // which is to be executed as well. + var successCallback = localCallback(mute); + + if (successCallback) { + successCallback(); + } + } else { + RTC.localVideo.setMute(!mute); + + this.hardMuteVideo(mute); + + this.modifySources(localCallback(mute)); + } +}; + +// SDP-based mute by going recvonly/sendrecv +// FIXME: should probably black out the screen as well +JingleSession.prototype.toggleVideoMute = function (callback) { + this.service.setVideoMute(RTC.localVideo.isMuted(), callback); +}; + +JingleSession.prototype.hardMuteVideo = function (muted) { + this.pendingop = muted ? 'mute' : 'unmute'; +}; + +JingleSession.prototype.sendMute = function (muted, content) { + var info = $iq({to: this.peerjid, + type: 'set'}) + .c('jingle', {xmlns: 'urn:xmpp:jingle:1', + action: 'session-info', + initiator: this.initiator, + sid: this.sid }); + info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'}); + info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'}); + if (content) { + info.attrs({'name': content}); + } + this.connection.send(info); +}; + +JingleSession.prototype.sendRinging = function () { + var info = $iq({to: this.peerjid, + type: 'set'}) + .c('jingle', {xmlns: 'urn:xmpp:jingle:1', + action: 'session-info', + initiator: this.initiator, + sid: this.sid }); + info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'}); + this.connection.send(info); +}; + +JingleSession.prototype.getStats = function (interval) { + var self = this; + var recv = {audio: 0, video: 0}; + var lost = {audio: 0, video: 0}; + var lastrecv = {audio: 0, video: 0}; + var lastlost = {audio: 0, video: 0}; + var loss = {audio: 0, video: 0}; + var delta = {audio: 0, video: 0}; + this.statsinterval = window.setInterval(function () { + if (self && self.peerconnection && self.peerconnection.getStats) { + self.peerconnection.getStats(function (stats) { + var results = stats.result(); + // TODO: there are so much statistics you can get from this.. + for (var i = 0; i < results.length; ++i) { + if (results[i].type == 'ssrc') { + var packetsrecv = results[i].stat('packetsReceived'); + var packetslost = results[i].stat('packetsLost'); + if (packetsrecv && packetslost) { + packetsrecv = parseInt(packetsrecv, 10); + packetslost = parseInt(packetslost, 10); + + if (results[i].stat('googFrameRateReceived')) { + lastlost.video = lost.video; + lastrecv.video = recv.video; + recv.video = packetsrecv; + lost.video = packetslost; + } else { + lastlost.audio = lost.audio; + lastrecv.audio = recv.audio; + recv.audio = packetsrecv; + lost.audio = packetslost; + } + } + } + } + delta.audio = recv.audio - lastrecv.audio; + delta.video = recv.video - lastrecv.video; + loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0; + loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0; + $(document).trigger('packetloss.jingle', [self.sid, loss]); + }); + } + }, interval || 3000); + return this.statsinterval; +}; + +JingleSession.onJingleError = function (session, error) +{ + console.error("Jingle error", error); +} + +JingleSession.onJingleFatalError = function (session, error) +{ + this.service.sessionTerminated = true; + connection.emuc.doLeave(); + UI.messageHandler.showError( "Sorry", + "Internal application error[setRemoteDescription]"); +} + +JingleSession.prototype.setLocalDescription = function () { + // put our ssrcs into presence so other clients can identify our stream + var newssrcs = []; + var media = simulcast.parseMedia(this.peerconnection.localDescription); + media.forEach(function (media) { + + if(Object.keys(media.sources).length > 0) { + // TODO(gp) maybe exclude FID streams? + Object.keys(media.sources).forEach(function (ssrc) { + newssrcs.push({ + 'ssrc': ssrc, + 'type': media.type, + 'direction': media.direction + }); + }); + } + else if(this.localStreamsSSRC && this.localStreamsSSRC[media.type]) + { + newssrcs.push({ + 'ssrc': this.localStreamsSSRC[media.type], + 'type': media.type, + 'direction': media.direction + }); + } + + }); + + console.log('new ssrcs', newssrcs); + + // Have to clear presence map to get rid of removed streams + this.connection.emuc.clearPresenceMedia(); + + if (newssrcs.length > 0) { + for (var i = 1; i <= newssrcs.length; i ++) { + // Change video type to screen + if (newssrcs[i-1].type === 'video' && desktopsharing.isUsingScreenStream()) { + newssrcs[i-1].type = 'screen'; + } + this.connection.emuc.addMediaToPresence(i, + newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction); + } + + this.connection.emuc.sendPresence(); + } +} + +// an attempt to work around https://github.com/jitsi/jitmeet/issues/32 +function sendKeyframe(pc) { + console.log('sendkeyframe', pc.iceConnectionState); + if (pc.iceConnectionState !== 'connected') return; // safe... + pc.setRemoteDescription( + pc.remoteDescription, + function () { + pc.createAnswer( + function (modifiedAnswer) { + pc.setLocalDescription( + modifiedAnswer, + function () { + // noop + }, + function (error) { + console.log('triggerKeyframe setLocalDescription failed', error); + UI.messageHandler.showError(); + } + ); + }, + function (error) { + console.log('triggerKeyframe createAnswer failed', error); + UI.messageHandler.showError(); + } + ); + }, + function (error) { + console.log('triggerKeyframe setRemoteDescription failed', error); + UI.messageHandler.showError(); + } + ); +} + + +JingleSession.prototype.remoteStreamAdded = function (data) { + var self = this; + var thessrc; + + // look up an associated JID for a stream id + if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) { + // look only at a=ssrc: and _not_ at a=ssrc-group: lines + + var ssrclines + = SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:'); + ssrclines = ssrclines.filter(function (line) { + // NOTE(gp) previously we filtered on the mslabel, but that property + // is not always present. + // return line.indexOf('mslabel:' + data.stream.label) !== -1; + + return ((line.indexOf('msid:' + data.stream.id) !== -1)); + }); + if (ssrclines.length) { + thessrc = ssrclines[0].substring(7).split(' ')[0]; + + // We signal our streams (through Jingle to the focus) before we set + // our presence (through which peers associate remote streams to + // jids). So, it might arrive that a remote stream is added but + // ssrc2jid is not yet updated and thus data.peerjid cannot be + // successfully set. Here we wait for up to a second for the + // presence to arrive. + + if (!ssrc2jid[thessrc]) { + // TODO(gp) limit wait duration to 1 sec. + setTimeout(function(d) { + return function() { + self.remoteStreamAdded(d); + } + }(data), 250); + return; + } + + // ok to overwrite the one from focus? might save work in colibri.js + console.log('associated jid', ssrc2jid[thessrc], data.peerjid); + if (ssrc2jid[thessrc]) { + data.peerjid = ssrc2jid[thessrc]; + } + } + } + + //TODO: this code should be removed when firefox implement multistream support + if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX) + { + if((notReceivedSSRCs.length == 0) || + !ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]]) + { + // TODO(gp) limit wait duration to 1 sec. + setTimeout(function(d) { + return function() { + self.remoteStreamAdded(d); + } + }(data), 250); + return; + } + + thessrc = notReceivedSSRCs.pop(); + if (ssrc2jid[thessrc]) { + data.peerjid = ssrc2jid[thessrc]; + } + } + + RTC.createRemoteStream(data, this.sid, thessrc); + + var isVideo = data.stream.getVideoTracks().length > 0; + // an attempt to work around https://github.com/jitsi/jitmeet/issues/32 + if (isVideo && + data.peerjid && this.peerjid === data.peerjid && + data.stream.getVideoTracks().length === 0 && + RTC.localVideo.getTracks().length > 0) { + window.setTimeout(function () { + sendKeyframe(self.peerconnection); + }, 3000); + } +} + +module.exports = JingleSession; +},{"./SDP":2,"./SDPDiffer":3,"./SDPUtil":4,"./TraceablePeerConnection":5}],2:[function(require,module,exports){ +/* jshint -W117 */ +var SDPUtil = require("./SDPUtil"); + +// SDP STUFF +function SDP(sdp) { + this.media = sdp.split('\r\nm='); + for (var i = 1; i < this.media.length; i++) { + this.media[i] = 'm=' + this.media[i]; + if (i != this.media.length - 1) { + this.media[i] += '\r\n'; + } + } + this.session = this.media.shift() + '\r\n'; + this.raw = this.session + this.media.join(''); +} +/** + * Returns map of MediaChannel mapped per channel idx. + */ +SDP.prototype.getMediaSsrcMap = function() { + var self = this; + var media_ssrcs = {}; + var tmp; + for (var mediaindex = 0; mediaindex < self.media.length; mediaindex++) { + tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc:'); + var mid = SDPUtil.parse_mid(SDPUtil.find_line(self.media[mediaindex], 'a=mid:')); + var media = { + mediaindex: mediaindex, + mid: mid, + ssrcs: {}, + ssrcGroups: [] + }; + media_ssrcs[mediaindex] = media; + tmp.forEach(function (line) { + var linessrc = line.substring(7).split(' ')[0]; + // allocate new ChannelSsrc + if(!media.ssrcs[linessrc]) { + media.ssrcs[linessrc] = { + ssrc: linessrc, + lines: [] + }; + } + media.ssrcs[linessrc].lines.push(line); + }); + tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:'); + tmp.forEach(function(line){ + var semantics = line.substr(0, idx).substr(13); + var ssrcs = line.substr(14 + semantics.length).split(' '); + if (ssrcs.length != 0) { + media.ssrcGroups.push({ + semantics: semantics, + ssrcs: ssrcs + }); + } + }); + } + return media_ssrcs; +}; +/** + * Returns true if this SDP contains given SSRC. + * @param ssrc the ssrc to check. + * @returns {boolean} true if this SDP contains given SSRC. + */ +SDP.prototype.containsSSRC = function(ssrc) { + var medias = this.getMediaSsrcMap(); + var contains = false; + Object.keys(medias).forEach(function(mediaindex){ + var media = medias[mediaindex]; + //console.log("Check", channel, ssrc); + if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){ + contains = true; + } + }); + return contains; +}; + + +// remove iSAC and CN from SDP +SDP.prototype.mangle = function () { + var i, j, mline, lines, rtpmap, newdesc; + for (i = 0; i < this.media.length; i++) { + lines = this.media[i].split('\r\n'); + lines.pop(); // remove empty last element + mline = SDPUtil.parse_mline(lines.shift()); + if (mline.media != 'audio') + continue; + newdesc = ''; + mline.fmt.length = 0; + for (j = 0; j < lines.length; j++) { + if (lines[j].substr(0, 9) == 'a=rtpmap:') { + rtpmap = SDPUtil.parse_rtpmap(lines[j]); + if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC') + continue; + mline.fmt.push(rtpmap.id); + newdesc += lines[j] + '\r\n'; + } else { + newdesc += lines[j] + '\r\n'; + } + } + this.media[i] = SDPUtil.build_mline(mline) + '\r\n'; + this.media[i] += newdesc; + } + this.raw = this.session + this.media.join(''); +}; + +// remove lines matching prefix from session section +SDP.prototype.removeSessionLines = function(prefix) { + var self = this; + var lines = SDPUtil.find_lines(this.session, prefix); + lines.forEach(function(line) { + self.session = self.session.replace(line + '\r\n', ''); + }); + this.raw = this.session + this.media.join(''); + return lines; +} +// remove lines matching prefix from a media section specified by mediaindex +// TODO: non-numeric mediaindex could match mid +SDP.prototype.removeMediaLines = function(mediaindex, prefix) { + var self = this; + var lines = SDPUtil.find_lines(this.media[mediaindex], prefix); + lines.forEach(function(line) { + self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', ''); + }); + this.raw = this.session + this.media.join(''); + return lines; +} + +// add content's to a jingle element +SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { +// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]); + var i, j, k, mline, ssrc, rtpmap, tmp, line, lines; + var self = this; + // new bundle plan + if (SDPUtil.find_line(this.session, 'a=group:')) { + lines = SDPUtil.find_lines(this.session, 'a=group:'); + for (i = 0; i < lines.length; i++) { + tmp = lines[i].split(' '); + var semantics = tmp.shift().substr(8); + elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics}); + for (j = 0; j < tmp.length; j++) { + elem.c('content', {name: tmp[j]}).up(); + } + elem.up(); + } + } + for (i = 0; i < this.media.length; i++) { + mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]); + if (!(mline.media === 'audio' || + mline.media === 'video' || + mline.media === 'application')) + { + continue; + } + if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) { + ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first + } else { + if(ssrcs && ssrcs[mline.media]) + { + ssrc = ssrcs[mline.media]; + } + else + ssrc = false; + } + + elem.c('content', {creator: thecreator, name: mline.media}); + if (SDPUtil.find_line(this.media[i], 'a=mid:')) { + // prefer identifier from a=mid if present + var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:')); + elem.attrs({ name: mid }); + } + + if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length) + { + elem.c('description', + {xmlns: 'urn:xmpp:jingle:apps:rtp:1', + media: mline.media }); + if (ssrc) { + elem.attrs({ssrc: ssrc}); + } + for (j = 0; j < mline.fmt.length; j++) { + rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]); + elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap)); + // put any 'a=fmtp:' + mline.fmt[j] lines into + if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) { + tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])); + for (k = 0; k < tmp.length; k++) { + elem.c('parameter', tmp[k]).up(); + } + } + this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb + + elem.up(); + } + if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) { + elem.c('encryption', {required: 1}); + var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session); + crypto.forEach(function(line) { + elem.c('crypto', SDPUtil.parse_crypto(line)).up(); + }); + elem.up(); // end of encryption + } + + if (ssrc) { + // new style mapping + elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); + // FIXME: group by ssrc and support multiple different ssrcs + var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:'); + if(ssrclines.length > 0) { + ssrclines.forEach(function (line) { + idx = line.indexOf(' '); + var linessrc = line.substr(0, idx).substr(7); + if (linessrc != ssrc) { + elem.up(); + ssrc = linessrc; + elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); + } + var kv = line.substr(idx + 1); + elem.c('parameter'); + if (kv.indexOf(':') == -1) { + elem.attrs({ name: kv }); + } else { + elem.attrs({ name: kv.split(':', 2)[0] }); + elem.attrs({ value: kv.split(':', 2)[1] }); + } + elem.up(); + }); + elem.up(); + } + else + { + elem.up(); + elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); + elem.c('parameter'); + elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)}); + elem.up(); + var msid = null; + if(mline.media == "audio") + { + msid = RTC.localAudio.getId(); + } + else + { + msid = RTC.localVideo.getId(); + } + if(msid != null) + { + msid = msid.replace(/[\{,\}]/g,""); + elem.c('parameter'); + elem.attrs({name: "msid", value:msid}); + elem.up(); + elem.c('parameter'); + elem.attrs({name: "mslabel", value:msid}); + elem.up(); + elem.c('parameter'); + elem.attrs({name: "label", value:msid}); + elem.up(); + elem.up(); + } + + + } + + // XEP-0339 handle ssrc-group attributes + var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:'); + ssrc_group_lines.forEach(function(line) { + idx = line.indexOf(' '); + var semantics = line.substr(0, idx).substr(13); + var ssrcs = line.substr(14 + semantics.length).split(' '); + if (ssrcs.length != 0) { + elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); + ssrcs.forEach(function(ssrc) { + elem.c('source', { ssrc: ssrc }) + .up(); + }); + elem.up(); + } + }); + } + + if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) { + elem.c('rtcp-mux').up(); + } + + // XEP-0293 -- map a=rtcp-fb:* + this.RtcpFbToJingle(i, elem, '*'); + + // XEP-0294 + if (SDPUtil.find_line(this.media[i], 'a=extmap:')) { + lines = SDPUtil.find_lines(this.media[i], 'a=extmap:'); + for (j = 0; j < lines.length; j++) { + tmp = SDPUtil.parse_extmap(lines[j]); + elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0', + uri: tmp.uri, + id: tmp.value }); + if (tmp.hasOwnProperty('direction')) { + switch (tmp.direction) { + case 'sendonly': + elem.attrs({senders: 'responder'}); + break; + case 'recvonly': + elem.attrs({senders: 'initiator'}); + break; + case 'sendrecv': + elem.attrs({senders: 'both'}); + break; + case 'inactive': + elem.attrs({senders: 'none'}); + break; + } + } + // TODO: handle params + elem.up(); + } + } + elem.up(); // end of description + } + + // map ice-ufrag/pwd, dtls fingerprint, candidates + this.TransportToJingle(i, elem); + + if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) { + elem.attrs({senders: 'both'}); + } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) { + elem.attrs({senders: 'initiator'}); + } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) { + elem.attrs({senders: 'responder'}); + } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) { + elem.attrs({senders: 'none'}); + } + if (mline.port == '0') { + // estos hack to reject an m-line + elem.attrs({senders: 'rejected'}); + } + elem.up(); // end of content + } + elem.up(); + return elem; +}; + +SDP.prototype.TransportToJingle = function (mediaindex, elem) { + var i = mediaindex; + var tmp; + var self = this; + elem.c('transport'); + + // XEP-0343 DTLS/SCTP + if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length) + { + var sctpmap = SDPUtil.find_line( + this.media[i], 'a=sctpmap:', self.session); + if (sctpmap) + { + var sctpAttrs = SDPUtil.parse_sctpmap(sctpmap); + elem.c('sctpmap', + { + xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1', + number: sctpAttrs[0], /* SCTP port */ + protocol: sctpAttrs[1], /* protocol */ + }); + // Optional stream count attribute + if (sctpAttrs.length > 2) + elem.attrs({ streams: sctpAttrs[2]}); + elem.up(); + } + } + // XEP-0320 + var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session); + fingerprints.forEach(function(line) { + tmp = SDPUtil.parse_fingerprint(line); + tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0'; + elem.c('fingerprint').t(tmp.fingerprint); + delete tmp.fingerprint; + line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session); + if (line) { + tmp.setup = line.substr(8); + } + elem.attrs(tmp); + elem.up(); // end of fingerprint + }); + tmp = SDPUtil.iceparams(this.media[mediaindex], this.session); + if (tmp) { + tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1'; + elem.attrs(tmp); + // XEP-0176 + if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines + var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session); + lines.forEach(function (line) { + elem.c('candidate', SDPUtil.candidateToJingle(line)).up(); + }); + } + } + elem.up(); // end of transport +} + +SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293 + var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype); + lines.forEach(function (line) { + var tmp = SDPUtil.parse_rtcpfb(line); + if (tmp.type == 'trr-int') { + elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]}); + elem.up(); + } else { + elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type}); + if (tmp.params.length > 0) { + elem.attrs({'subtype': tmp.params[0]}); + } + elem.up(); + } + }); +}; + +SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293 + var media = ''; + var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]'); + if (tmp.length) { + media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' '; + if (tmp.attr('value')) { + media += tmp.attr('value'); + } else { + media += '0'; + } + media += '\r\n'; + } + tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]'); + tmp.each(function () { + media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type'); + if ($(this).attr('subtype')) { + media += ' ' + $(this).attr('subtype'); + } + media += '\r\n'; + }); + return media; +}; + +// construct an SDP from a jingle stanza +SDP.prototype.fromJingle = function (jingle) { + var self = this; + this.raw = 'v=0\r\n' + + 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME + 's=-\r\n' + + 't=0 0\r\n'; + // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8 + if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) { + $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) { + var contents = $(group).find('>content').map(function (idx, content) { + return content.getAttribute('name'); + }).get(); + if (contents.length > 0) { + self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n'; + } + }); + } + + this.session = this.raw; + jingle.find('>content').each(function () { + var m = self.jingle2media($(this)); + self.media.push(m); + }); + + // reconstruct msid-semantic -- apparently not necessary + /* + var msid = SDPUtil.parse_ssrc(this.raw); + if (msid.hasOwnProperty('mslabel')) { + this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n"; + } + */ + + this.raw = this.session + this.media.join(''); +}; + +// translate a jingle content element into an an SDP media part +SDP.prototype.jingle2media = function (content) { + var media = '', + desc = content.find('description'), + ssrc = desc.attr('ssrc'), + self = this, + tmp; + var sctp = content.find( + '>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]'); + + tmp = { media: desc.attr('media') }; + tmp.port = '1'; + if (content.attr('senders') == 'rejected') { + // estos hack to reject an m-line. + tmp.port = '0'; + } + if (content.find('>transport>fingerprint').length || desc.find('encryption').length) { + if (sctp.length) + tmp.proto = 'DTLS/SCTP'; + else + tmp.proto = 'RTP/SAVPF'; + } else { + tmp.proto = 'RTP/AVPF'; + } + if (!sctp.length) + { + tmp.fmt = desc.find('payload-type').map( + function () { return this.getAttribute('id'); }).get(); + media += SDPUtil.build_mline(tmp) + '\r\n'; + } + else + { + media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n'; + media += 'a=sctpmap:' + sctp.attr('number') + + ' ' + sctp.attr('protocol'); + + var streamCount = sctp.attr('streams'); + if (streamCount) + media += ' ' + streamCount + '\r\n'; + else + media += '\r\n'; + } + + media += 'c=IN IP4 0.0.0.0\r\n'; + if (!sctp.length) + media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n'; + tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]'); + if (tmp.length) { + if (tmp.attr('ufrag')) { + media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n'; + } + if (tmp.attr('pwd')) { + media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n'; + } + tmp.find('>fingerprint').each(function () { + // FIXME: check namespace at some point + media += 'a=fingerprint:' + this.getAttribute('hash'); + media += ' ' + $(this).text(); + media += '\r\n'; + if (this.getAttribute('setup')) { + media += 'a=setup:' + this.getAttribute('setup') + '\r\n'; + } + }); + } + switch (content.attr('senders')) { + case 'initiator': + media += 'a=sendonly\r\n'; + break; + case 'responder': + media += 'a=recvonly\r\n'; + break; + case 'none': + media += 'a=inactive\r\n'; + break; + case 'both': + media += 'a=sendrecv\r\n'; + break; + } + media += 'a=mid:' + content.attr('name') + '\r\n'; + + // + // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though + // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html + if (desc.find('rtcp-mux').length) { + media += 'a=rtcp-mux\r\n'; + } + + if (desc.find('encryption').length) { + desc.find('encryption>crypto').each(function () { + media += 'a=crypto:' + this.getAttribute('tag'); + media += ' ' + this.getAttribute('crypto-suite'); + media += ' ' + this.getAttribute('key-params'); + if (this.getAttribute('session-params')) { + media += ' ' + this.getAttribute('session-params'); + } + media += '\r\n'; + }); + } + desc.find('payload-type').each(function () { + media += SDPUtil.build_rtpmap(this) + '\r\n'; + if ($(this).find('>parameter').length) { + media += 'a=fmtp:' + this.getAttribute('id') + ' '; + media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; '); + media += '\r\n'; + } + // xep-0293 + media += self.RtcpFbFromJingle($(this), this.getAttribute('id')); + }); + + // xep-0293 + media += self.RtcpFbFromJingle(desc, '*'); + + // xep-0294 + tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]'); + tmp.each(function () { + media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n'; + }); + + content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () { + media += SDPUtil.candidateFromJingle(this); + }); + + // XEP-0339 handle ssrc-group attributes + tmp = content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() { + var semantics = this.getAttribute('semantics'); + var ssrcs = $(this).find('>source').map(function() { + return this.getAttribute('ssrc'); + }).get(); + + if (ssrcs.length != 0) { + media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n'; + } + }); + + tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); + tmp.each(function () { + var ssrc = this.getAttribute('ssrc'); + $(this).find('>parameter').each(function () { + media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name'); + if (this.getAttribute('value') && this.getAttribute('value').length) + media += ':' + this.getAttribute('value'); + media += '\r\n'; + }); + }); + + return media; +}; + + +module.exports = SDP; + + +},{"./SDPUtil":4}],3:[function(require,module,exports){ +function SDPDiffer(mySDP, otherSDP) { + this.mySDP = mySDP; + this.otherSDP = otherSDP; +} + +/** + * Returns map of MediaChannel that contains only media not contained in otherSdp. Mapped by channel idx. + * @param otherSdp the other SDP to check ssrc with. + */ +SDPDiffer.prototype.getNewMedia = function() { + + // this could be useful in Array.prototype. + function arrayEquals(array) { + // if the other array is a falsy value, return + if (!array) + return false; + + // compare lengths - can save a lot of time + if (this.length != array.length) + return false; + + for (var i = 0, l=this.length; i < l; i++) { + // Check if we have nested arrays + if (this[i] instanceof Array && array[i] instanceof Array) { + // recurse into the nested arrays + if (!this[i].equals(array[i])) + return false; + } + else if (this[i] != array[i]) { + // Warning - two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + return true; + } + + var myMedias = this.mySDP.getMediaSsrcMap(); + var othersMedias = this.otherSDP.getMediaSsrcMap(); + var newMedia = {}; + Object.keys(othersMedias).forEach(function(othersMediaIdx) { + var myMedia = myMedias[othersMediaIdx]; + var othersMedia = othersMedias[othersMediaIdx]; + if(!myMedia && othersMedia) { + // Add whole channel + newMedia[othersMediaIdx] = othersMedia; + return; + } + // Look for new ssrcs accross the channel + Object.keys(othersMedia.ssrcs).forEach(function(ssrc) { + if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) { + // Allocate channel if we've found ssrc that doesn't exist in our channel + if(!newMedia[othersMediaIdx]){ + newMedia[othersMediaIdx] = { + mediaindex: othersMedia.mediaindex, + mid: othersMedia.mid, + ssrcs: {}, + ssrcGroups: [] + }; + } + newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc]; + } + }); + + // Look for new ssrc groups across the channels + othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){ + + // try to match the other ssrc-group with an ssrc-group of ours + var matched = false; + for (var i = 0; i < myMedia.ssrcGroups.length; i++) { + var mySsrcGroup = myMedia.ssrcGroups[i]; + if (otherSsrcGroup.semantics == mySsrcGroup.semantics + && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) { + + matched = true; + break; + } + } + + if (!matched) { + // Allocate channel if we've found an ssrc-group that doesn't + // exist in our channel + + if(!newMedia[othersMediaIdx]){ + newMedia[othersMediaIdx] = { + mediaindex: othersMedia.mediaindex, + mid: othersMedia.mid, + ssrcs: {}, + ssrcGroups: [] + }; + } + newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup); + } + }); + }); + return newMedia; +}; + +/** + * Sends SSRC update IQ. + * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove. + * @param sid session identifier that will be put into the IQ. + * @param initiator initiator identifier. + * @param toJid destination Jid + * @param isAdd indicates if this is remove or add operation. + */ +SDPDiffer.prototype.toJingle = function(modify) { + var sdpMediaSsrcs = this.getNewMedia(); + var self = this; + + // FIXME: only announce video ssrcs since we mix audio and dont need + // the audio ssrcs therefore + var modified = false; + Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){ + modified = true; + var media = sdpMediaSsrcs[mediaindex]; + modify.c('content', {name: media.mid}); + + modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid}); + // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly + // generate sources from lines + Object.keys(media.ssrcs).forEach(function(ssrcNum) { + var mediaSsrc = media.ssrcs[ssrcNum]; + modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); + modify.attrs({ssrc: mediaSsrc.ssrc}); + // iterate over ssrc lines + mediaSsrc.lines.forEach(function (line) { + var idx = line.indexOf(' '); + var kv = line.substr(idx + 1); + modify.c('parameter'); + if (kv.indexOf(':') == -1) { + modify.attrs({ name: kv }); + } else { + modify.attrs({ name: kv.split(':', 2)[0] }); + modify.attrs({ value: kv.split(':', 2)[1] }); + } + modify.up(); // end of parameter + }); + modify.up(); // end of source + }); + + // generate source groups from lines + media.ssrcGroups.forEach(function(ssrcGroup) { + if (ssrcGroup.ssrcs.length != 0) { + + modify.c('ssrc-group', { + semantics: ssrcGroup.semantics, + xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' + }); + + ssrcGroup.ssrcs.forEach(function (ssrc) { + modify.c('source', { ssrc: ssrc }) + .up(); // end of source + }); + modify.up(); // end of ssrc-group + } + }); + + modify.up(); // end of description + modify.up(); // end of content + }); + + return modified; +}; + +module.exports = SDPDiffer; +},{}],4:[function(require,module,exports){ +SDPUtil = { + iceparams: function (mediadesc, sessiondesc) { + var data = null; + if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) && + SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) { + data = { + ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)), + pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) + }; + } + return data; + }, + parse_iceufrag: function (line) { + return line.substring(12); + }, + build_iceufrag: function (frag) { + return 'a=ice-ufrag:' + frag; + }, + parse_icepwd: function (line) { + return line.substring(10); + }, + build_icepwd: function (pwd) { + return 'a=ice-pwd:' + pwd; + }, + parse_mid: function (line) { + return line.substring(6); + }, + parse_mline: function (line) { + var parts = line.substring(2).split(' '), + data = {}; + data.media = parts.shift(); + data.port = parts.shift(); + data.proto = parts.shift(); + if (parts[parts.length - 1] === '') { // trailing whitespace + parts.pop(); + } + data.fmt = parts; + return data; + }, + build_mline: function (mline) { + return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' '); + }, + parse_rtpmap: function (line) { + var parts = line.substring(9).split(' '), + data = {}; + data.id = parts.shift(); + parts = parts[0].split('/'); + data.name = parts.shift(); + data.clockrate = parts.shift(); + data.channels = parts.length ? parts.shift() : '1'; + return data; + }, + /** + * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it. + * @param line eg. "a=sctpmap:5000 webrtc-datachannel" + * @returns [SCTP port number, protocol, streams] + */ + parse_sctpmap: function (line) + { + var parts = line.substring(10).split(' '); + var sctpPort = parts[0]; + var protocol = parts[1]; + // Stream count is optional + var streamCount = parts.length > 2 ? parts[2] : null; + return [sctpPort, protocol, streamCount];// SCTP port + }, + build_rtpmap: function (el) { + var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate'); + if (el.getAttribute('channels') && el.getAttribute('channels') != '1') { + line += '/' + el.getAttribute('channels'); + } + return line; + }, + parse_crypto: function (line) { + var parts = line.substring(9).split(' '), + data = {}; + data.tag = parts.shift(); + data['crypto-suite'] = parts.shift(); + data['key-params'] = parts.shift(); + if (parts.length) { + data['session-params'] = parts.join(' '); + } + return data; + }, + parse_fingerprint: function (line) { // RFC 4572 + var parts = line.substring(14).split(' '), + data = {}; + data.hash = parts.shift(); + data.fingerprint = parts.shift(); + // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ? + return data; + }, + parse_fmtp: function (line) { + var parts = line.split(' '), + i, key, value, + data = []; + parts.shift(); + parts = parts.join(' ').split(';'); + for (i = 0; i < parts.length; i++) { + key = parts[i].split('=')[0]; + while (key.length && key[0] == ' ') { + key = key.substring(1); + } + value = parts[i].split('=')[1]; + if (key && value) { + data.push({name: key, value: value}); + } else if (key) { + // rfc 4733 (DTMF) style stuff + data.push({name: '', value: key}); + } + } + return data; + }, + parse_icecandidate: function (line) { + var candidate = {}, + elems = line.split(' '); + candidate.foundation = elems[0].substring(12); + candidate.component = elems[1]; + candidate.protocol = elems[2].toLowerCase(); + candidate.priority = elems[3]; + candidate.ip = elems[4]; + candidate.port = elems[5]; + // elems[6] => "typ" + candidate.type = elems[7]; + candidate.generation = 0; // default value, may be overwritten below + for (var i = 8; i < elems.length; i += 2) { + switch (elems[i]) { + case 'raddr': + candidate['rel-addr'] = elems[i + 1]; + break; + case 'rport': + candidate['rel-port'] = elems[i + 1]; + break; + case 'generation': + candidate.generation = elems[i + 1]; + break; + case 'tcptype': + candidate.tcptype = elems[i + 1]; + break; + default: // TODO + console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"'); + } + } + candidate.network = '1'; + candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random + return candidate; + }, + build_icecandidate: function (cand) { + var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' '); + line += ' '; + switch (cand.type) { + case 'srflx': + case 'prflx': + case 'relay': + if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) { + line += 'raddr'; + line += ' '; + line += cand['rel-addr']; + line += ' '; + line += 'rport'; + line += ' '; + line += cand['rel-port']; + line += ' '; + } + break; + } + if (cand.hasOwnAttribute('tcptype')) { + line += 'tcptype'; + line += ' '; + line += cand.tcptype; + line += ' '; + } + line += 'generation'; + line += ' '; + line += cand.hasOwnAttribute('generation') ? cand.generation : '0'; + return line; + }, + parse_ssrc: function (desc) { + // proprietary mapping of a=ssrc lines + // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs + // and parse according to that + var lines = desc.split('\r\n'), + data = {}; + for (var i = 0; i < lines.length; i++) { + if (lines[i].substring(0, 7) == 'a=ssrc:') { + var idx = lines[i].indexOf(' '); + data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1]; + } + } + return data; + }, + parse_rtcpfb: function (line) { + var parts = line.substr(10).split(' '); + var data = {}; + data.pt = parts.shift(); + data.type = parts.shift(); + data.params = parts; + return data; + }, + parse_extmap: function (line) { + var parts = line.substr(9).split(' '); + var data = {}; + data.value = parts.shift(); + if (data.value.indexOf('/') != -1) { + data.direction = data.value.substr(data.value.indexOf('/') + 1); + data.value = data.value.substr(0, data.value.indexOf('/')); + } else { + data.direction = 'both'; + } + data.uri = parts.shift(); + data.params = parts; + return data; + }, + find_line: function (haystack, needle, sessionpart) { + var lines = haystack.split('\r\n'); + for (var i = 0; i < lines.length; i++) { + if (lines[i].substring(0, needle.length) == needle) { + return lines[i]; + } + } + if (!sessionpart) { + return false; + } + // search session part + lines = sessionpart.split('\r\n'); + for (var j = 0; j < lines.length; j++) { + if (lines[j].substring(0, needle.length) == needle) { + return lines[j]; + } + } + return false; + }, + find_lines: function (haystack, needle, sessionpart) { + var lines = haystack.split('\r\n'), + needles = []; + for (var i = 0; i < lines.length; i++) { + if (lines[i].substring(0, needle.length) == needle) + needles.push(lines[i]); + } + if (needles.length || !sessionpart) { + return needles; + } + // search session part + lines = sessionpart.split('\r\n'); + for (var j = 0; j < lines.length; j++) { + if (lines[j].substring(0, needle.length) == needle) { + needles.push(lines[j]); + } + } + return needles; + }, + candidateToJingle: function (line) { + // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0 + // + if (line.indexOf('candidate:') === 0) { + line = 'a=' + line; + } else if (line.substring(0, 12) != 'a=candidate:') { + console.log('parseCandidate called with a line that is not a candidate line'); + console.log(line); + return null; + } + if (line.substring(line.length - 2) == '\r\n') // chomp it + line = line.substring(0, line.length - 2); + var candidate = {}, + elems = line.split(' '), + i; + if (elems[6] != 'typ') { + console.log('did not find typ in the right place'); + console.log(line); + return null; + } + candidate.foundation = elems[0].substring(12); + candidate.component = elems[1]; + candidate.protocol = elems[2].toLowerCase(); + candidate.priority = elems[3]; + candidate.ip = elems[4]; + candidate.port = elems[5]; + // elems[6] => "typ" + candidate.type = elems[7]; + + candidate.generation = '0'; // default, may be overwritten below + for (i = 8; i < elems.length; i += 2) { + switch (elems[i]) { + case 'raddr': + candidate['rel-addr'] = elems[i + 1]; + break; + case 'rport': + candidate['rel-port'] = elems[i + 1]; + break; + case 'generation': + candidate.generation = elems[i + 1]; + break; + case 'tcptype': + candidate.tcptype = elems[i + 1]; + break; + default: // TODO + console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"'); + } + } + candidate.network = '1'; + candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random + return candidate; + }, + candidateFromJingle: function (cand) { + var line = 'a=candidate:'; + line += cand.getAttribute('foundation'); + line += ' '; + line += cand.getAttribute('component'); + line += ' '; + line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this + line += ' '; + line += cand.getAttribute('priority'); + line += ' '; + line += cand.getAttribute('ip'); + line += ' '; + line += cand.getAttribute('port'); + line += ' '; + line += 'typ'; + line += ' ' + cand.getAttribute('type'); + line += ' '; + switch (cand.getAttribute('type')) { + case 'srflx': + case 'prflx': + case 'relay': + if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) { + line += 'raddr'; + line += ' '; + line += cand.getAttribute('rel-addr'); + line += ' '; + line += 'rport'; + line += ' '; + line += cand.getAttribute('rel-port'); + line += ' '; + } + break; + } + if (cand.getAttribute('protocol').toLowerCase() == 'tcp') { + line += 'tcptype'; + line += ' '; + line += cand.getAttribute('tcptype'); + line += ' '; + } + line += 'generation'; + line += ' '; + line += cand.getAttribute('generation') || '0'; + return line + '\r\n'; + } +}; +module.exports = SDPUtil; +},{}],5:[function(require,module,exports){ +function TraceablePeerConnection(ice_config, constraints) { + var self = this; + var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection; + this.peerconnection = new RTCPeerconnection(ice_config, constraints); + this.updateLog = []; + this.stats = {}; + this.statsinterval = null; + this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable + + // override as desired + this.trace = function (what, info) { + //console.warn('WTRACE', what, info); + self.updateLog.push({ + time: new Date(), + type: what, + value: info || "" + }); + }; + this.onicecandidate = null; + this.peerconnection.onicecandidate = function (event) { + self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' ')); + if (self.onicecandidate !== null) { + self.onicecandidate(event); + } + }; + this.onaddstream = null; + this.peerconnection.onaddstream = function (event) { + self.trace('onaddstream', event.stream.id); + if (self.onaddstream !== null) { + self.onaddstream(event); + } + }; + this.onremovestream = null; + this.peerconnection.onremovestream = function (event) { + self.trace('onremovestream', event.stream.id); + if (self.onremovestream !== null) { + self.onremovestream(event); + } + }; + this.onsignalingstatechange = null; + this.peerconnection.onsignalingstatechange = function (event) { + self.trace('onsignalingstatechange', self.signalingState); + if (self.onsignalingstatechange !== null) { + self.onsignalingstatechange(event); + } + }; + this.oniceconnectionstatechange = null; + this.peerconnection.oniceconnectionstatechange = function (event) { + self.trace('oniceconnectionstatechange', self.iceConnectionState); + if (self.oniceconnectionstatechange !== null) { + self.oniceconnectionstatechange(event); + } + }; + this.onnegotiationneeded = null; + this.peerconnection.onnegotiationneeded = function (event) { + self.trace('onnegotiationneeded'); + if (self.onnegotiationneeded !== null) { + self.onnegotiationneeded(event); + } + }; + self.ondatachannel = null; + this.peerconnection.ondatachannel = function (event) { + self.trace('ondatachannel', event); + if (self.ondatachannel !== null) { + self.ondatachannel(event); + } + }; + if (!navigator.mozGetUserMedia && this.maxstats) { + this.statsinterval = window.setInterval(function() { + self.peerconnection.getStats(function(stats) { + var results = stats.result(); + for (var i = 0; i < results.length; ++i) { + //console.log(results[i].type, results[i].id, results[i].names()) + var now = new Date(); + results[i].names().forEach(function (name) { + var id = results[i].id + '-' + name; + if (!self.stats[id]) { + self.stats[id] = { + startTime: now, + endTime: now, + values: [], + times: [] + }; + } + self.stats[id].values.push(results[i].stat(name)); + self.stats[id].times.push(now.getTime()); + if (self.stats[id].values.length > self.maxstats) { + self.stats[id].values.shift(); + self.stats[id].times.shift(); + } + self.stats[id].endTime = now; + }); + } + }); + + }, 1000); + } +}; + +dumpSDP = function(description) { + return 'type: ' + description.type + '\r\n' + description.sdp; +} + +if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) { + TraceablePeerConnection.prototype.__defineGetter__('signalingState', function() { return this.peerconnection.signalingState; }); + TraceablePeerConnection.prototype.__defineGetter__('iceConnectionState', function() { return this.peerconnection.iceConnectionState; }); + TraceablePeerConnection.prototype.__defineGetter__('localDescription', function() { + var publicLocalDescription = simulcast.reverseTransformLocalDescription(this.peerconnection.localDescription); + return publicLocalDescription; + }); + TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() { + var publicRemoteDescription = simulcast.reverseTransformRemoteDescription(this.peerconnection.remoteDescription); + return publicRemoteDescription; + }); +} + +TraceablePeerConnection.prototype.addStream = function (stream) { + this.trace('addStream', stream.id); + simulcast.resetSender(); + try + { + this.peerconnection.addStream(stream); + } + catch (e) + { + console.error(e); + return; + } +}; + +TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) { + this.trace('removeStream', stream.id); + simulcast.resetSender(); + if(stopStreams) { + stream.getAudioTracks().forEach(function (track) { + track.stop(); + }); + stream.getVideoTracks().forEach(function (track) { + track.stop(); + }); + } + this.peerconnection.removeStream(stream); +}; + +TraceablePeerConnection.prototype.createDataChannel = function (label, opts) { + this.trace('createDataChannel', label, opts); + return this.peerconnection.createDataChannel(label, opts); +}; + +TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) { + var self = this; + description = simulcast.transformLocalDescription(description); + this.trace('setLocalDescription', dumpSDP(description)); + this.peerconnection.setLocalDescription(description, + function () { + self.trace('setLocalDescriptionOnSuccess'); + successCallback(); + }, + function (err) { + self.trace('setLocalDescriptionOnFailure', err); + failureCallback(err); + } + ); + /* + if (this.statsinterval === null && this.maxstats > 0) { + // start gathering stats + } + */ +}; + +TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) { + var self = this; + description = simulcast.transformRemoteDescription(description); + this.trace('setRemoteDescription', dumpSDP(description)); + this.peerconnection.setRemoteDescription(description, + function () { + self.trace('setRemoteDescriptionOnSuccess'); + successCallback(); + }, + function (err) { + self.trace('setRemoteDescriptionOnFailure', err); + failureCallback(err); + } + ); + /* + if (this.statsinterval === null && this.maxstats > 0) { + // start gathering stats + } + */ +}; + +TraceablePeerConnection.prototype.close = function () { + this.trace('stop'); + if (this.statsinterval !== null) { + window.clearInterval(this.statsinterval); + this.statsinterval = null; + } + this.peerconnection.close(); +}; + +TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) { + var self = this; + this.trace('createOffer', JSON.stringify(constraints, null, ' ')); + this.peerconnection.createOffer( + function (offer) { + self.trace('createOfferOnSuccess', dumpSDP(offer)); + successCallback(offer); + }, + function(err) { + self.trace('createOfferOnFailure', err); + failureCallback(err); + }, + constraints + ); +}; + +TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) { + var self = this; + this.trace('createAnswer', JSON.stringify(constraints, null, ' ')); + this.peerconnection.createAnswer( + function (answer) { + answer = simulcast.transformAnswer(answer); + self.trace('createAnswerOnSuccess', dumpSDP(answer)); + successCallback(answer); + }, + function(err) { + self.trace('createAnswerOnFailure', err); + failureCallback(err); + }, + constraints + ); +}; + +TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) { + var self = this; + this.trace('addIceCandidate', JSON.stringify(candidate, null, ' ')); + this.peerconnection.addIceCandidate(candidate); + /* maybe later + this.peerconnection.addIceCandidate(candidate, + function () { + self.trace('addIceCandidateOnSuccess'); + successCallback(); + }, + function (err) { + self.trace('addIceCandidateOnFailure', err); + failureCallback(err); + } + ); + */ +}; + +TraceablePeerConnection.prototype.getStats = function(callback, errback) { + if (navigator.mozGetUserMedia) { + // ignore for now... + if(!errback) + errback = function () { + + } + this.peerconnection.getStats(null,callback,errback); + } else { + this.peerconnection.getStats(callback); + } +}; + +module.exports = TraceablePeerConnection; + + +},{}],6:[function(require,module,exports){ +/* global $, $iq, config, connection, UI, messageHandler, + roomName, sessionTerminated, Strophe, Util */ +/** + * Contains logic responsible for enabling/disabling functionality available + * only to moderator users. + */ +var connection = null; +var focusUserJid; +var getNextTimeout = Util.createExpBackoffTimer(1000); +var getNextErrorTimeout = Util.createExpBackoffTimer(1000); +// External authentication stuff +var externalAuthEnabled = false; +// Sip gateway can be enabled by configuring Jigasi host in config.js or +// it will be enabled automatically if focus detects the component through +// service discovery. +var sipGatewayEnabled = config.hosts.call_control !== undefined; + +var Moderator = { + isModerator: function () { + return connection && connection.emuc.isModerator(); + }, + + isPeerModerator: function (peerJid) { + return connection && + connection.emuc.getMemberRole(peerJid) === 'moderator'; + }, + + isExternalAuthEnabled: function () { + return externalAuthEnabled; + }, + + isSipGatewayEnabled: function () { + return sipGatewayEnabled; + }, + + setConnection: function (con) { + connection = con; + }, + + init: function (xmpp) { + this.xmppService = xmpp; + this.onLocalRoleChange = function (from, member, pres) { + UI.onModeratorStatusChanged(Moderator.isModerator()); + }; + }, + + onMucLeft: function (jid) { + console.info("Someone left is it focus ? " + jid); + var resource = Strophe.getResourceFromJid(jid); + if (resource === 'focus' && !this.xmppService.sessionTerminated) { + console.info( + "Focus has left the room - leaving conference"); + //hangUp(); + // We'd rather reload to have everything re-initialized + // FIXME: show some message before reload + location.reload(); + } + }, + + setFocusUserJid: function (focusJid) { + if (!focusUserJid) { + focusUserJid = focusJid; + console.info("Focus jid set to: " + focusUserJid); + } + }, + + getFocusUserJid: function () { + return focusUserJid; + }, + + getFocusComponent: function () { + // Get focus component address + var focusComponent = config.hosts.focus; + // If not specified use default: 'focus.domain' + if (!focusComponent) { + focusComponent = 'focus.' + config.hosts.domain; + } + return focusComponent; + }, + + createConferenceIq: function (roomName) { + // Generate create conference IQ + var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'}); + elem.c('conference', { + xmlns: 'http://jitsi.org/protocol/focus', + room: roomName + }); + if (config.hosts.bridge !== undefined) { + elem.c( + 'property', + { name: 'bridge', value: config.hosts.bridge}) + .up(); + } + // Tell the focus we have Jigasi configured + if (config.hosts.call_control !== undefined) { + elem.c( + 'property', + { name: 'call_control', value: config.hosts.call_control}) + .up(); + } + if (config.channelLastN !== undefined) { + elem.c( + 'property', + { name: 'channelLastN', value: config.channelLastN}) + .up(); + } + if (config.adaptiveLastN !== undefined) { + elem.c( + 'property', + { name: 'adaptiveLastN', value: config.adaptiveLastN}) + .up(); + } + if (config.adaptiveSimulcast !== undefined) { + elem.c( + 'property', + { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast}) + .up(); + } + if (config.openSctp !== undefined) { + elem.c( + 'property', + { name: 'openSctp', value: config.openSctp}) + .up(); + } + if (config.enableFirefoxSupport !== undefined) { + elem.c( + 'property', + { name: 'enableFirefoxHacks', + value: config.enableFirefoxSupport}) + .up(); + } + elem.up(); + return elem; + }, + + parseConfigOptions: function (resultIq) { + + Moderator.setFocusUserJid( + $(resultIq).find('conference').attr('focusjid')); + + var extAuthParam + = $(resultIq).find('>conference>property[name=\'externalAuth\']'); + if (extAuthParam.length) { + externalAuthEnabled = extAuthParam.attr('value') === 'true'; + } + + console.info("External authentication enabled: " + externalAuthEnabled); + + // Check if focus has auto-detected Jigasi component(this will be also + // included if we have passed our host from the config) + if ($(resultIq).find( + '>conference>property[name=\'sipGatewayEnabled\']').length) { + sipGatewayEnabled = true; + } + + console.info("Sip gateway enabled: " + sipGatewayEnabled); + }, + + // FIXME: we need to show the fact that we're waiting for the focus + // to the user(or that focus is not available) + allocateConferenceFocus: function (roomName, callback) { + // Try to use focus user JID from the config + Moderator.setFocusUserJid(config.focusUserJid); + // Send create conference IQ + var iq = Moderator.createConferenceIq(roomName); + connection.sendIQ( + iq, + function (result) { + if ('true' === $(result).find('conference').attr('ready')) { + // Reset both timers + getNextTimeout(true); + getNextErrorTimeout(true); + // Setup config options + Moderator.parseConfigOptions(result); + // Exec callback + callback(); + } else { + var waitMs = getNextTimeout(); + console.info("Waiting for the focus... " + waitMs); + // Reset error timeout + getNextErrorTimeout(true); + window.setTimeout( + function () { + Moderator.allocateConferenceFocus( + roomName, callback); + }, waitMs); + } + }, + function (error) { + // Not authorized to create new room + if ($(error).find('>error>not-authorized').length) { + console.warn("Unauthorized to start the conference"); + UI.onAuthenticationRequired(function () { + Moderator.allocateConferenceFocus(roomName, callback); + }); + return; + } + var waitMs = getNextErrorTimeout(); + console.error("Focus error, retry after " + waitMs, error); + // Show message + UI.messageHandler.notify( + 'Conference focus', 'disconnected', + Moderator.getFocusComponent() + + ' not available - retry in ' + + (waitMs / 1000) + ' sec'); + // Reset response timeout + getNextTimeout(true); + window.setTimeout( + function () { + Moderator.allocateConferenceFocus(roomName, callback); + }, waitMs); + } + ); + }, + + getAuthUrl: function (roomName, urlCallback) { + var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'}); + iq.c('auth-url', { + xmlns: 'http://jitsi.org/protocol/focus', + room: roomName + }); + connection.sendIQ( + iq, + function (result) { + var url = $(result).find('auth-url').attr('url'); + if (url) { + console.info("Got auth url: " + url); + urlCallback(url); + } else { + console.error( + "Failed to get auth url fro mthe focus", result); + } + }, + function (error) { + console.error("Get auth url error", error); + } + ); + } +}; + +module.exports = Moderator; + + + + +},{}],7:[function(require,module,exports){ +/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator, + Toolbar, Util */ +var Moderator = require("./moderator"); + + +var recordingToken = null; +var recordingEnabled; + +/** + * Whether to use a jirecon component for recording, or use the videobridge + * through COLIBRI. + */ +var useJirecon = (typeof config.hosts.jirecon != "undefined"); + +/** + * The ID of the jirecon recording session. Jirecon generates it when we + * initially start recording, and it needs to be used in subsequent requests + * to jirecon. + */ +var jireconRid = null; + +function setRecordingToken(token) { + recordingToken = token; +} + +function setRecording(state, token, callback) { + if (useJirecon){ + this.setRecordingJirecon(state, token, callback); + } else { + this.setRecordingColibri(state, token, callback); + } +} + +function setRecordingJirecon(state, token, callback) { + if (state == recordingEnabled){ + return; + } + + var iq = $iq({to: config.hosts.jirecon, type: 'set'}) + .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon', + action: state ? 'start' : 'stop', + mucjid: connection.emuc.roomjid}); + if (!state){ + iq.attrs({rid: jireconRid}); + } + + console.log('Start recording'); + + connection.sendIQ( + iq, + function (result) { + // TODO wait for an IQ with the real status, since this is + // provisional? + jireconRid = $(result).find('recording').attr('rid'); + console.log('Recording ' + (state ? 'started' : 'stopped') + + '(jirecon)' + result); + recordingEnabled = state; + if (!state){ + jireconRid = null; + } + + callback(state); + }, + function (error) { + console.log('Failed to start recording, error: ', error); + callback(recordingEnabled); + }); +} + +// Sends a COLIBRI message which enables or disables (according to 'state') +// the recording on the bridge. Waits for the result IQ and calls 'callback' +// with the new recording state, according to the IQ. +function setRecordingColibri(state, token, callback) { + var elem = $iq({to: focusMucJid, type: 'set'}); + elem.c('conference', { + xmlns: 'http://jitsi.org/protocol/colibri' + }); + elem.c('recording', {state: state, token: token}); + + connection.sendIQ(elem, + function (result) { + console.log('Set recording "', state, '". Result:', result); + var recordingElem = $(result).find('>conference>recording'); + var newState = ('true' === recordingElem.attr('state')); + + recordingEnabled = newState; + callback(newState); + }, + function (error) { + console.warn(error); + callback(recordingEnabled); + } + ); +} + +var Recording = { + toggleRecording: function (tokenEmptyCallback, + startingCallback, startedCallback) { + if (!Moderator.isModerator()) { + console.log( + 'non-focus, or conference not yet organized:' + + ' not enabling recording'); + return; + } + + // Jirecon does not (currently) support a token. + if (!recordingToken && !useJirecon) { + tokenEmptyCallback(function (value) { + setRecordingToken(value); + this.toggleRecording(); + }); + + return; + } + + var oldState = recordingEnabled; + startingCallback(!oldState); + setRecording(!oldState, + recordingToken, + function (state) { + console.log("New recording state: ", state); + if (state === oldState) { + // FIXME: new focus: + // this will not work when moderator changes + // during active session. Then it will assume that + // recording status has changed to true, but it might have + // been already true(and we only received actual status from + // the focus). + // + // SO we start with status null, so that it is initialized + // here and will fail only after second click, so if invalid + // token was used we have to press the button twice before + // current status will be fetched and token will be reset. + // + // Reliable way would be to return authentication error. + // Or status update when moderator connects. + // Or we have to stop recording session when current + // moderator leaves the room. + + // Failed to change, reset the token because it might + // have been wrong + setRecordingToken(null); + } + startedCallback(state); + + } + ); + } + +} + +module.exports = Recording; +},{"./moderator":6}],8:[function(require,module,exports){ +/* jshint -W117 */ +/* a simple MUC connection plugin + * can only handle a single MUC room + */ + +var bridgeIsDown = false; + +var Moderator = require("./moderator"); + +module.exports = function(XMPP, eventEmitter) { + Strophe.addConnectionPlugin('emuc', { + connection: null, + roomjid: null, + myroomjid: null, + members: {}, + list_members: [], // so we can elect a new focus + presMap: {}, + preziMap: {}, + joined: false, + isOwner: false, + role: null, + init: function (conn) { + this.connection = conn; + }, + initPresenceMap: function (myroomjid) { + this.presMap['to'] = myroomjid; + this.presMap['xns'] = 'http://jabber.org/protocol/muc'; + }, + doJoin: function (jid, password) { + this.myroomjid = jid; + + console.info("Joined MUC as " + this.myroomjid); + + this.initPresenceMap(this.myroomjid); + + if (!this.roomjid) { + this.roomjid = Strophe.getBareJidFromJid(jid); + // add handlers (just once) + this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true}); + this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true}); + this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true}); + this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true}); + } + if (password !== undefined) { + this.presMap['password'] = password; + } + this.sendPresence(); + }, + doLeave: function () { + console.log("do leave", this.myroomjid); + var pres = $pres({to: this.myroomjid, type: 'unavailable' }); + this.presMap.length = 0; + this.connection.send(pres); + }, + createNonAnonymousRoom: function () { + // http://xmpp.org/extensions/xep-0045.html#createroom-reserved + + var getForm = $iq({type: 'get', to: this.roomjid}) + .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}) + .c('x', {xmlns: 'jabber:x:data', type: 'submit'}); + + this.connection.sendIQ(getForm, function (form) { + + if (!$(form).find( + '>query>x[xmlns="jabber:x:data"]' + + '>field[var="muc#roomconfig_whois"]').length) { + + console.error('non-anonymous rooms not supported'); + return; + } + + var formSubmit = $iq({to: this.roomjid, type: 'set'}) + .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}); + + formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'}); + + formSubmit.c('field', {'var': 'FORM_TYPE'}) + .c('value') + .t('http://jabber.org/protocol/muc#roomconfig').up().up(); + + formSubmit.c('field', {'var': 'muc#roomconfig_whois'}) + .c('value').t('anyone').up().up(); + + this.connection.sendIQ(formSubmit); + + }, function (error) { + console.error("Error getting room configuration form"); + }); + }, + onPresence: function (pres) { + var from = pres.getAttribute('from'); + + // What is this for? A workaround for something? + if (pres.getAttribute('type')) { + return true; + } + + // Parse etherpad tag. + var etherpad = $(pres).find('>etherpad'); + if (etherpad.length) { + if (config.etherpad_base && !Moderator.isModerator()) { + UI.initEtherpad(etherpad.text()); + } + } + + // Parse prezi tag. + var presentation = $(pres).find('>prezi'); + if (presentation.length) { + var url = presentation.attr('url'); + var current = presentation.find('>current').text(); + + console.log('presentation info received from', from, url); + + if (this.preziMap[from] == null) { + this.preziMap[from] = url; + + $(document).trigger('presentationadded.muc', [from, url, current]); + } + else { + $(document).trigger('gotoslide.muc', [from, url, current]); + } + } + else if (this.preziMap[from] != null) { + var url = this.preziMap[from]; + delete this.preziMap[from]; + $(document).trigger('presentationremoved.muc', [from, url]); + } + + // Parse audio info tag. + var audioMuted = $(pres).find('>audiomuted'); + if (audioMuted.length) { + $(document).trigger('audiomuted.muc', [from, audioMuted.text()]); + } + + // Parse video info tag. + var videoMuted = $(pres).find('>videomuted'); + if (videoMuted.length) { + $(document).trigger('videomuted.muc', [from, videoMuted.text()]); + } + + var stats = $(pres).find('>stats'); + if (stats.length) { + var statsObj = {}; + Strophe.forEachChild(stats[0], "stat", function (el) { + statsObj[el.getAttribute("name")] = el.getAttribute("value"); + }); + connectionquality.updateRemoteStats(from, statsObj); + } + + // Parse status. + if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) { + this.isOwner = true; + this.createNonAnonymousRoom(); + } + + // Parse roles. + var member = {}; + member.show = $(pres).find('>show').text(); + member.status = $(pres).find('>status').text(); + var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item'); + member.affiliation = tmp.attr('affiliation'); + member.role = tmp.attr('role'); + + // Focus recognition + member.jid = tmp.attr('jid'); + member.isFocus = false; + if (member.jid + && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) { + member.isFocus = true; + } + + var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]'); + member.displayName = (nicktag.length > 0 ? nicktag.html() : null); + + if (from == this.myroomjid) { + if (member.affiliation == 'owner') this.isOwner = true; + if (this.role !== member.role) { + this.role = member.role; + if (Moderator.onLocalRoleChange) + Moderator.onLocalRoleChange(from, member, pres); + UI.onLocalRoleChange(from, member, pres); + } + if (!this.joined) { + this.joined = true; + eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member); + this.list_members.push(from); + } + } else if (this.members[from] === undefined) { + // new participant + this.members[from] = member; + this.list_members.push(from); + console.log('entered', from, member); + if (member.isFocus) { + focusMucJid = from; + console.info("Ignore focus: " + from + ", real JID: " + member.jid); + } + else { + var id = $(pres).find('>userID').text(); + var email = $(pres).find('>email'); + if (email.length > 0) { + id = email.text(); + } + UI.onMucEntered(from, id, member.displayName); + API.triggerEvent("participantJoined", {jid: from}); + } + } else { + // Presence update for existing participant + // Watch role change: + if (this.members[from].role != member.role) { + this.members[from].role = member.role; + UI.onMucRoleChanged(member.role, member.displayName); + } + } + + // Always trigger presence to update bindings + $(document).trigger('presence.muc', [from, member, pres]); + this.parsePresence(from, member, pres); + + // Trigger status message update + if (member.status) { + UI.onMucPresenceStatus(from, member); + } + + return true; + }, + onPresenceUnavailable: function (pres) { + var from = pres.getAttribute('from'); + // Status code 110 indicates that this notification is "self-presence". + if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) { + delete this.members[from]; + this.list_members.splice(this.list_members.indexOf(from), 1); + this.onParticipantLeft(from); + } + // If the status code is 110 this means we're leaving and we would like + // to remove everyone else from our view, so we trigger the event. + else if (this.list_members.length > 1) { + for (var i = 0; i < this.list_members.length; i++) { + var member = this.list_members[i]; + delete this.members[i]; + this.list_members.splice(i, 1); + this.onParticipantLeft(member); + } + } + if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) { + $(document).trigger('kicked.muc', [from]); + if (this.myroomjid === from) { + XMPP.disposeConference(false); + eventEmitter.emit(XMPPEvents.KICKED); + } + } + return true; + }, + onPresenceError: function (pres) { + var from = pres.getAttribute('from'); + if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { + console.log('on password required', from); + var self = this; + UI.onPasswordReqiured(function (value) { + self.doJoin(from, value); + }); + } else if ($(pres).find( + '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { + var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to')); + if (toDomain === config.hosts.anonymousdomain) { + // we are connected with anonymous domain and only non anonymous users can create rooms + // we must authorize the user + XMPP.promptLogin(); + } else { + console.warn('onPresError ', pres); + UI.messageHandler.openReportDialog(null, + 'Oops! Something went wrong and we couldn`t connect to the conference.', + pres); + } + } else { + console.warn('onPresError ', pres); + UI.messageHandler.openReportDialog(null, + 'Oops! Something went wrong and we couldn`t connect to the conference.', + pres); + } + return true; + }, + sendMessage: function (body, nickname) { + var msg = $msg({to: this.roomjid, type: 'groupchat'}); + msg.c('body', body).up(); + if (nickname) { + msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up(); + } + this.connection.send(msg); + API.triggerEvent("outgoingMessage", {"message": body}); + }, + setSubject: function (subject) { + var msg = $msg({to: this.roomjid, type: 'groupchat'}); + msg.c('subject', subject); + this.connection.send(msg); + console.log("topic changed to " + subject); + }, + onMessage: function (msg) { + // FIXME: this is a hack. but jingle on muc makes nickchanges hard + var from = msg.getAttribute('from'); + var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from); + + var txt = $(msg).find('>body').text(); + var type = msg.getAttribute("type"); + if (type == "error") { + UI.chatAddError($(msg).find('>text').text(), txt); + return true; + } + + var subject = $(msg).find('>subject'); + if (subject.length) { + var subjectText = subject.text(); + if (subjectText || subjectText == "") { + UI.chatSetSubject(subjectText); + console.log("Subject is changed to " + subjectText); + } + } + + + if (txt) { + console.log('chat', nick, txt); + UI.updateChatConversation(from, nick, txt); + if (from != this.myroomjid) + API.triggerEvent("incomingMessage", + {"from": from, "nick": nick, "message": txt}); + } + return true; + }, + lockRoom: function (key, onSuccess, onError, onNotSupported) { + //http://xmpp.org/extensions/xep-0045.html#roomconfig + var ob = this; + this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}), + function (res) { + if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) { + var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}); + formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'}); + formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up(); + formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up(); + // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373 + formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up(); + // FIXME: is muc#roomconfig_passwordprotectedroom required? + this.connection.sendIQ(formsubmit, + onSuccess, + onError); + } else { + onNotSupported(); + } + }, onError); + }, + kick: function (jid) { + var kickIQ = $iq({to: this.roomjid, type: 'set'}) + .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'}) + .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'}) + .c('reason').t('You have been kicked.').up().up().up(); + + this.connection.sendIQ( + kickIQ, + function (result) { + console.log('Kick participant with jid: ', jid, result); + }, + function (error) { + console.log('Kick participant error: ', error); + }); + }, + sendPresence: function () { + var pres = $pres({to: this.presMap['to'] }); + pres.c('x', {xmlns: this.presMap['xns']}); + + if (this.presMap['password']) { + pres.c('password').t(this.presMap['password']).up(); + } + + pres.up(); + + // Send XEP-0115 'c' stanza that contains our capabilities info + if (this.connection.caps) { + this.connection.caps.node = config.clientNode; + pres.c('c', this.connection.caps.generateCapsAttrs()).up(); + } + + pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'}) + .t(navigator.userAgent).up(); + + if (this.presMap['bridgeIsDown']) { + pres.c('bridgeIsDown').up(); + } + + if (this.presMap['email']) { + pres.c('email').t(this.presMap['email']).up(); + } + + if (this.presMap['userId']) { + pres.c('userId').t(this.presMap['userId']).up(); + } + + if (this.presMap['displayName']) { + // XEP-0172 + pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}) + .t(this.presMap['displayName']).up(); + } + + if (this.presMap['audions']) { + pres.c('audiomuted', {xmlns: this.presMap['audions']}) + .t(this.presMap['audiomuted']).up(); + } + + if (this.presMap['videons']) { + pres.c('videomuted', {xmlns: this.presMap['videons']}) + .t(this.presMap['videomuted']).up(); + } + + if (this.presMap['statsns']) { + var stats = pres.c('stats', {xmlns: this.presMap['statsns']}); + for (var stat in this.presMap["stats"]) + if (this.presMap["stats"][stat] != null) + stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up(); + pres.up(); + } + + if (this.presMap['prezins']) { + pres.c('prezi', + {xmlns: this.presMap['prezins'], + 'url': this.presMap['preziurl']}) + .c('current').t(this.presMap['prezicurrent']).up().up(); + } + + if (this.presMap['etherpadns']) { + pres.c('etherpad', {xmlns: this.presMap['etherpadns']}) + .t(this.presMap['etherpadname']).up(); + } + + if (this.presMap['medians']) { + pres.c('media', {xmlns: this.presMap['medians']}); + var sourceNumber = 0; + Object.keys(this.presMap).forEach(function (key) { + if (key.indexOf('source') >= 0) { + sourceNumber++; + } + }); + if (sourceNumber > 0) + for (var i = 1; i <= sourceNumber / 3; i++) { + pres.c('source', + {type: this.presMap['source' + i + '_type'], + ssrc: this.presMap['source' + i + '_ssrc'], + direction: this.presMap['source' + i + '_direction'] + || 'sendrecv' } + ).up(); + } + } + + pres.up(); +// console.debug(pres.toString()); + this.connection.send(pres); + }, + addDisplayNameToPresence: function (displayName) { + this.presMap['displayName'] = displayName; + }, + addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) { + if (!this.presMap['medians']) + this.presMap['medians'] = 'http://estos.de/ns/mjs'; + + this.presMap['source' + sourceNumber + '_type'] = mtype; + this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs; + this.presMap['source' + sourceNumber + '_direction'] = direction; + }, + clearPresenceMedia: function () { + var self = this; + Object.keys(this.presMap).forEach(function (key) { + if (key.indexOf('source') != -1) { + delete self.presMap[key]; + } + }); + }, + addPreziToPresence: function (url, currentSlide) { + this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi'; + this.presMap['preziurl'] = url; + this.presMap['prezicurrent'] = currentSlide; + }, + removePreziFromPresence: function () { + delete this.presMap['prezins']; + delete this.presMap['preziurl']; + delete this.presMap['prezicurrent']; + }, + addCurrentSlideToPresence: function (currentSlide) { + this.presMap['prezicurrent'] = currentSlide; + }, + getPrezi: function (roomjid) { + return this.preziMap[roomjid]; + }, + addEtherpadToPresence: function (etherpadName) { + this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad'; + this.presMap['etherpadname'] = etherpadName; + }, + addAudioInfoToPresence: function (isMuted) { + this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio'; + this.presMap['audiomuted'] = isMuted.toString(); + }, + addVideoInfoToPresence: function (isMuted) { + this.presMap['videons'] = 'http://jitsi.org/jitmeet/video'; + this.presMap['videomuted'] = isMuted.toString(); + }, + addConnectionInfoToPresence: function (stats) { + this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats'; + this.presMap['stats'] = stats; + }, + findJidFromResource: function (resourceJid) { + if (resourceJid && + resourceJid === Strophe.getResourceFromJid(this.myroomjid)) { + return this.myroomjid; + } + var peerJid = null; + Object.keys(this.members).some(function (jid) { + peerJid = jid; + return Strophe.getResourceFromJid(jid) === resourceJid; + }); + return peerJid; + }, + addBridgeIsDownToPresence: function () { + this.presMap['bridgeIsDown'] = true; + }, + addEmailToPresence: function (email) { + this.presMap['email'] = email; + }, + addUserIdToPresence: function (userId) { + this.presMap['userId'] = userId; + }, + isModerator: function () { + return this.role === 'moderator'; + }, + getMemberRole: function (peerJid) { + if (this.members[peerJid]) { + return this.members[peerJid].role; + } + return null; + }, + onParticipantLeft: function (jid) { + UI.onMucLeft(jid); + + API.triggerEvent("participantLeft", {jid: jid}); + + delete jid2Ssrc[jid]; + + this.connection.jingle.terminateByJid(jid); + + if (this.getPrezi(jid)) { + $(document).trigger('presentationremoved.muc', + [jid, this.getPrezi(jid)]); + } + + Moderator.onMucLeft(jid); + }, + parsePresence: function (from, memeber, pres) { + if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) { + bridgeIsDown = true; + eventEmitter.emit(XMPPEvents.BRIDGE_DOWN); + } + + if(memeber.isFocus) + return; + + // Remove old ssrcs coming from the jid + Object.keys(ssrc2jid).forEach(function (ssrc) { + if (ssrc2jid[ssrc] == jid) { + delete ssrc2jid[ssrc]; + delete ssrc2videoType[ssrc]; + } + }); + + var changedStreams = []; + $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) { + //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc')); + var ssrcV = ssrc.getAttribute('ssrc'); + ssrc2jid[ssrcV] = from; + notReceivedSSRCs.push(ssrcV); + + var type = ssrc.getAttribute('type'); + ssrc2videoType[ssrcV] = type; + + var direction = ssrc.getAttribute('direction'); + + changedStreams.push({type: type, direction: direction}); + + }); + + eventEmitter.emit(XMPPEvents.CHANGED_STREAMS, from, changedStreams); + + var displayName = !config.displayJids + ? memeber.displayName : Strophe.getResourceFromJid(from); + + if (displayName && displayName.length > 0) + { +// $(document).trigger('displaynamechanged', +// [jid, displayName]); + eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName); + } + + + var id = $(pres).find('>userID').text(); + var email = $(pres).find('>email'); + if(email.length > 0) { + id = email.text(); + } + + eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id); + } + }); +}; + + +},{"./moderator":6}],9:[function(require,module,exports){ +/* jshint -W117 */ + +var JingleSession = require("./JingleSession"); + +function CallIncomingJingle(sid, connection) { + var sess = connection.jingle.sessions[sid]; + + // TODO: do we check activecall == null? + activecall = sess; + + statistics.onConferenceCreated(sess); + RTC.onConferenceCreated(sess); + + // TODO: check affiliation and/or role + console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]); + sess.usedrip = true; // not-so-naive trickle ice + sess.sendAnswer(); + sess.accept(); + +}; + +module.exports = function(XMPP) +{ + Strophe.addConnectionPlugin('jingle', { + connection: null, + sessions: {}, + jid2session: {}, + ice_config: {iceServers: []}, + pc_constraints: {}, + media_constraints: { + mandatory: { + 'OfferToReceiveAudio': true, + 'OfferToReceiveVideo': true + } + // MozDontOfferDataChannel: true when this is firefox + }, + init: function (conn) { + this.connection = conn; + if (this.connection.disco) { + // http://xmpp.org/extensions/xep-0167.html#support + // http://xmpp.org/extensions/xep-0176.html#support + this.connection.disco.addFeature('urn:xmpp:jingle:1'); + this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1'); + this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1'); + this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio'); + this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video'); + + + // this is dealt with by SDP O/A so we don't need to annouce this + //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293 + //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294 + if (config.useRtcpMux) { + this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux + } + if (config.useBundle) { + this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle + } + //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc + } + this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null); + }, + onJingle: function (iq) { + var sid = $(iq).find('jingle').attr('sid'); + var action = $(iq).find('jingle').attr('action'); + var fromJid = iq.getAttribute('from'); + // send ack first + var ack = $iq({type: 'result', + to: fromJid, + id: iq.getAttribute('id') + }); + console.log('on jingle ' + action + ' from ' + fromJid, iq); + var sess = this.sessions[sid]; + if ('session-initiate' != action) { + if (sess === null) { + ack.type = 'error'; + ack.c('error', {type: 'cancel'}) + .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up() + .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'}); + this.connection.send(ack); + return true; + } + // compare from to sess.peerjid (bare jid comparison for later compat with message-mode) + // local jid is not checked + if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) { + console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid); + ack.type = 'error'; + ack.c('error', {type: 'cancel'}) + .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up() + .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'}); + this.connection.send(ack); + return true; + } + } else if (sess !== undefined) { + // existing session with same session id + // this might be out-of-order if the sess.peerjid is the same as from + ack.type = 'error'; + ack.c('error', {type: 'cancel'}) + .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up(); + console.warn('duplicate session id', sid); + this.connection.send(ack); + return true; + } + // FIXME: check for a defined action + this.connection.send(ack); + // see http://xmpp.org/extensions/xep-0166.html#concepts-session + switch (action) { + case 'session-initiate': + sess = new JingleSession( + $(iq).attr('to'), $(iq).find('jingle').attr('sid'), + this.connection, XMPP); + // configure session + + sess.media_constraints = this.media_constraints; + sess.pc_constraints = this.pc_constraints; + sess.ice_config = this.ice_config; + + sess.initiate(fromJid, false); + // FIXME: setRemoteDescription should only be done when this call is to be accepted + sess.setRemoteDescription($(iq).find('>jingle'), 'offer'); + + this.sessions[sess.sid] = sess; + this.jid2session[sess.peerjid] = sess; + + // the callback should either + // .sendAnswer and .accept + // or .sendTerminate -- not necessarily synchronus + CallIncomingJingle(sess.sid, this.connection); + break; + case 'session-accept': + sess.setRemoteDescription($(iq).find('>jingle'), 'answer'); + sess.accept(); + $(document).trigger('callaccepted.jingle', [sess.sid]); + break; + case 'session-terminate': + // If this is not the focus sending the terminate, we have + // nothing more to do here. + if (Object.keys(this.sessions).length < 1 + || !(this.sessions[Object.keys(this.sessions)[0]] + instanceof JingleSession)) + { + break; + } + console.log('terminating...', sess.sid); + sess.terminate(); + this.terminate(sess.sid); + if ($(iq).find('>jingle>reason').length) { + $(document).trigger('callterminated.jingle', [ + sess.sid, + sess.peerjid, + $(iq).find('>jingle>reason>:first')[0].tagName, + $(iq).find('>jingle>reason>text').text() + ]); + } else { + $(document).trigger('callterminated.jingle', + [sess.sid, sess.peerjid]); + } + break; + case 'transport-info': + sess.addIceCandidate($(iq).find('>jingle>content')); + break; + case 'session-info': + var affected; + if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { + $(document).trigger('ringing.jingle', [sess.sid]); + } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { + affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name'); + $(document).trigger('mute.jingle', [sess.sid, affected]); + } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { + affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name'); + $(document).trigger('unmute.jingle', [sess.sid, affected]); + } + break; + case 'addsource': // FIXME: proprietary, un-jingleish + case 'source-add': // FIXME: proprietary + sess.addSource($(iq).find('>jingle>content'), fromJid); + break; + case 'removesource': // FIXME: proprietary, un-jingleish + case 'source-remove': // FIXME: proprietary + sess.removeSource($(iq).find('>jingle>content'), fromJid); + break; + default: + console.warn('jingle action not implemented', action); + break; + } + return true; + }, + initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid + var sess = new JingleSession(myjid || this.connection.jid, + Math.random().toString(36).substr(2, 12), // random string + this.connection, XMPP); + // configure session + + sess.media_constraints = this.media_constraints; + sess.pc_constraints = this.pc_constraints; + sess.ice_config = this.ice_config; + + sess.initiate(peerjid, true); + this.sessions[sess.sid] = sess; + this.jid2session[sess.peerjid] = sess; + sess.sendOffer(); + return sess; + }, + terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions) + if (sid === null || sid === undefined) { + for (sid in this.sessions) { + if (this.sessions[sid].state != 'ended') { + this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text); + this.sessions[sid].terminate(); + } + delete this.jid2session[this.sessions[sid].peerjid]; + delete this.sessions[sid]; + } + } else if (this.sessions.hasOwnProperty(sid)) { + if (this.sessions[sid].state != 'ended') { + this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text); + this.sessions[sid].terminate(); + } + delete this.jid2session[this.sessions[sid].peerjid]; + delete this.sessions[sid]; + } + }, + // Used to terminate a session when an unavailable presence is received. + terminateByJid: function (jid) { + if (this.jid2session.hasOwnProperty(jid)) { + var sess = this.jid2session[jid]; + if (sess) { + sess.terminate(); + console.log('peer went away silently', jid); + delete this.sessions[sess.sid]; + delete this.jid2session[jid]; + $(document).trigger('callterminated.jingle', + [sess.sid, jid], 'gone'); + } + } + }, + terminateRemoteByJid: function (jid, reason) { + if (this.jid2session.hasOwnProperty(jid)) { + var sess = this.jid2session[jid]; + if (sess) { + sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null); + sess.terminate(); + console.log('terminate peer with jid', sess.sid, jid); + delete this.sessions[sess.sid]; + delete this.jid2session[jid]; + $(document).trigger('callterminated.jingle', + [sess.sid, jid, 'kicked']); + } + } + }, + getStunAndTurnCredentials: function () { + // get stun and turn configuration from server via xep-0215 + // uses time-limited credentials as described in + // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 + // + // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua + // for a prosody module which implements this + // + // currently, this doesn't work with updateIce and therefore credentials with a long + // validity have to be fetched before creating the peerconnection + // TODO: implement refresh via updateIce as described in + // https://code.google.com/p/webrtc/issues/detail?id=1650 + var self = this; + this.connection.sendIQ( + $iq({type: 'get', to: this.connection.domain}) + .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}), + function (res) { + var iceservers = []; + $(res).find('>services>service').each(function (idx, el) { + el = $(el); + var dict = {}; + var type = el.attr('type'); + switch (type) { + case 'stun': + dict.url = 'stun:' + el.attr('host'); + if (el.attr('port')) { + dict.url += ':' + el.attr('port'); + } + iceservers.push(dict); + break; + case 'turn': + case 'turns': + dict.url = type + ':'; + if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508 + if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) { + dict.url += el.attr('username') + '@'; + } else { + dict.username = el.attr('username'); // only works in M28 + } + } + dict.url += el.attr('host'); + if (el.attr('port') && el.attr('port') != '3478') { + dict.url += ':' + el.attr('port'); + } + if (el.attr('transport') && el.attr('transport') != 'udp') { + dict.url += '?transport=' + el.attr('transport'); + } + if (el.attr('password')) { + dict.credential = el.attr('password'); + } + iceservers.push(dict); + break; + } + }); + self.ice_config.iceServers = iceservers; + }, + function (err) { + console.warn('getting turn credentials failed', err); + console.warn('is mod_turncredentials or similar installed?'); + } + ); + // implement push? + }, + + /** + * Populates the log data + */ + populateData: function () { + var data = {}; + Object.keys(this.sessions).forEach(function (sid) { + var session = this.sessions[sid]; + if (session.peerconnection && session.peerconnection.updateLog) { + // FIXME: should probably be a .dump call + data["jingle_" + session.sid] = { + updateLog: session.peerconnection.updateLog, + stats: session.peerconnection.stats, + url: window.location.href + }; + } + }); + return data; + } + }); +}; + + +},{"./JingleSession":1}],10:[function(require,module,exports){ +/* global Strophe */ +module.exports = function () { + + Strophe.addConnectionPlugin('logger', { + // logs raw stanzas and makes them available for download as JSON + connection: null, + log: [], + init: function (conn) { + this.connection = conn; + this.connection.rawInput = this.log_incoming.bind(this); + this.connection.rawOutput = this.log_outgoing.bind(this); + }, + log_incoming: function (stanza) { + this.log.push([new Date().getTime(), 'incoming', stanza]); + }, + log_outgoing: function (stanza) { + this.log.push([new Date().getTime(), 'outgoing', stanza]); + } + }); +}; +},{}],11:[function(require,module,exports){ +/* global $, $iq, config, connection, focusMucJid, forceMuted, + setAudioMuted, Strophe */ +/** + * Moderate connection plugin. + */ +module.exports = function (XMPP) { + Strophe.addConnectionPlugin('moderate', { + connection: null, + init: function (conn) { + this.connection = conn; + + this.connection.addHandler(this.onMute.bind(this), + 'http://jitsi.org/jitmeet/audio', + 'iq', + 'set', + null, + null); + }, + setMute: function (jid, mute) { + console.info("set mute", mute); + var iqToFocus = $iq({to: focusMucJid, type: 'set'}) + .c('mute', { + xmlns: 'http://jitsi.org/jitmeet/audio', + jid: jid + }) + .t(mute.toString()) + .up(); + + this.connection.sendIQ( + iqToFocus, + function (result) { + console.log('set mute', result); + }, + function (error) { + console.log('set mute error', error); + }); + }, + onMute: function (iq) { + var from = iq.getAttribute('from'); + if (from !== focusMucJid) { + console.warn("Ignored mute from non focus peer"); + return false; + } + var mute = $(iq).find('mute'); + if (mute.length) { + var doMuteAudio = mute.text() === "true"; + UI.setAudioMuted(doMuteAudio); + XMPP.forceMuted = doMuteAudio; + } + return true; + }, + eject: function (jid) { + // We're not the focus, so can't terminate + //connection.jingle.terminateRemoteByJid(jid, 'kick'); + this.connection.emuc.kick(jid); + } + }); +} +},{}],12:[function(require,module,exports){ +/* jshint -W117 */ +module.exports = function() { + Strophe.addConnectionPlugin('rayo', + { + RAYO_XMLNS: 'urn:xmpp:rayo:1', + connection: null, + init: function (conn) { + this.connection = conn; + if (this.connection.disco) { + this.connection.disco.addFeature('urn:xmpp:rayo:client:1'); + } + + this.connection.addHandler( + this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null); + }, + onRayo: function (iq) { + console.info("Rayo IQ", iq); + }, + dial: function (to, from, roomName, roomPass) { + var self = this; + var req = $iq( + { + type: 'set', + to: focusMucJid + } + ); + req.c('dial', + { + xmlns: this.RAYO_XMLNS, + to: to, + from: from + }); + req.c('header', + { + name: 'JvbRoomName', + value: roomName + }).up(); + + if (roomPass !== null && roomPass.length) { + + req.c('header', + { + name: 'JvbRoomPassword', + value: roomPass + }).up(); + } + + this.connection.sendIQ( + req, + function (result) { + console.info('Dial result ', result); + + var resource = $(result).find('ref').attr('uri'); + this.call_resource = resource.substr('xmpp:'.length); + console.info( + "Received call resource: " + this.call_resource); + }, + function (error) { + console.info('Dial error ', error); + } + ); + }, + hang_up: function () { + if (!this.call_resource) { + console.warn("No call in progress"); + return; + } + + var self = this; + var req = $iq( + { + type: 'set', + to: this.call_resource + } + ); + req.c('hangup', + { + xmlns: this.RAYO_XMLNS + }); + + this.connection.sendIQ( + req, + function (result) { + console.info('Hangup result ', result); + self.call_resource = null; + }, + function (error) { + console.info('Hangup error ', error); + self.call_resource = null; + } + ); + } + } + ); +}; + +},{}],13:[function(require,module,exports){ +/** + * Strophe logger implementation. Logs from level WARN and above. + */ +module.exports = function () { + + Strophe.log = function (level, msg) { + switch (level) { + case Strophe.LogLevel.WARN: + console.warn("Strophe: " + msg); + break; + case Strophe.LogLevel.ERROR: + case Strophe.LogLevel.FATAL: + console.error("Strophe: " + msg); + break; + } + }; + + Strophe.getStatusString = function (status) { + switch (status) { + case Strophe.Status.ERROR: + return "ERROR"; + case Strophe.Status.CONNECTING: + return "CONNECTING"; + case Strophe.Status.CONNFAIL: + return "CONNFAIL"; + case Strophe.Status.AUTHENTICATING: + return "AUTHENTICATING"; + case Strophe.Status.AUTHFAIL: + return "AUTHFAIL"; + case Strophe.Status.CONNECTED: + return "CONNECTED"; + case Strophe.Status.DISCONNECTED: + return "DISCONNECTED"; + case Strophe.Status.DISCONNECTING: + return "DISCONNECTING"; + case Strophe.Status.ATTACHED: + return "ATTACHED"; + default: + return "unknown"; + } + }; +}; + +},{}],14:[function(require,module,exports){ +var Moderator = require("./moderator"); +var EventEmitter = require("events"); +var Recording = require("./recording"); +var SDP = require("./SDP"); + +var eventEmitter = new EventEmitter(); +var connection = null; +var authenticatedUser = false; +var activecall = null; + +function connect(jid, password, uiCredentials) { + var bosh + = uiCredentials.bosh || config.bosh || '/http-bind'; + connection = new Strophe.Connection(bosh); + Moderator.setConnection(connection); + + var settings = UI.getSettings(); + var email = settings.email; + var displayName = settings.displayName; + if(email) { + connection.emuc.addEmailToPresence(email); + } else { + connection.emuc.addUserIdToPresence(settings.uid); + } + if(displayName) { + connection.emuc.addDisplayNameToPresence(displayName); + } + + if (connection.disco) { + // for chrome, add multistream cap + } + connection.jingle.pc_constraints = RTC.getPCConstraints(); + if (config.useIPv6) { + // https://code.google.com/p/webrtc/issues/detail?id=2828 + if (!connection.jingle.pc_constraints.optional) + connection.jingle.pc_constraints.optional = []; + connection.jingle.pc_constraints.optional.push({googIPv6: true}); + } + + if(!password) + password = uiCredentials.password; + + var anonymousConnectionFailed = false; + connection.connect(jid, password, function (status, msg) { + console.log('Strophe status changed to', + Strophe.getStatusString(status)); + if (status === Strophe.Status.CONNECTED) { + if (config.useStunTurn) { + connection.jingle.getStunAndTurnCredentials(); + } + UI.disableConnect(); + + console.info("My Jabber ID: " + connection.jid); + + if(password) + authenticatedUser = true; + maybeDoJoin(); + } else if (status === Strophe.Status.CONNFAIL) { + if(msg === 'x-strophe-bad-non-anon-jid') { + anonymousConnectionFailed = true; + } + } else if (status === Strophe.Status.DISCONNECTED) { + if(anonymousConnectionFailed) { + // prompt user for username and password + XMPP.promptLogin(); + } + } else if (status === Strophe.Status.AUTHFAIL) { + // wrong password or username, prompt user + XMPP.promptLogin(); + + } + }); +} + + + +function maybeDoJoin() { + if (connection && connection.connected && + Strophe.getResourceFromJid(connection.jid) + && (RTC.localAudio || RTC.localVideo)) { + // .connected is true while connecting? + doJoin(); + } +} + +function doJoin() { + var roomName = UI.generateRoomName(); + + Moderator.allocateConferenceFocus( + roomName, UI.checkForNicknameAndJoin); +} + +function initStrophePlugins() +{ + require("./strophe.emuc")(XMPP, eventEmitter); + require("./strophe.jingle")(); + require("./strophe.moderate")(XMPP); + require("./strophe.util")(); + require("./strophe.rayo")(); + require("./strophe.logger")(); +} + +function registerListeners() { + RTC.addStreamListener(maybeDoJoin, + StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); +} + +function setupEvents() { + $(window).bind('beforeunload', function () { + if (connection && connection.connected) { + // ensure signout + $.ajax({ + type: 'POST', + url: config.bosh, + async: false, + cache: false, + contentType: 'application/xml', + data: "" + + "" + + "", + success: function (data) { + console.log('signed out'); + console.log(data); + }, + error: function (XMLHttpRequest, textStatus, errorThrown) { + console.log('signout error', + textStatus + ' (' + errorThrown + ')'); + } + }); + } + XMPP.disposeConference(true); + }); +} + +var XMPP = { + sessionTerminated: false, + /** + * Remembers if we were muted by the focus. + * @type {boolean} + */ + forceMuted: false, + start: function (uiCredentials) { + setupEvents(); + initStrophePlugins(); + registerListeners(); + Moderator.init(); + var jid = uiCredentials.jid || + config.hosts.anonymousdomain || + config.hosts.domain || + window.location.hostname; + connect(jid, null, uiCredentials); + }, + promptLogin: function () { + UI.showLoginPopup(connect); + }, + joinRooom: function(roomName, useNicks, nick) + { + var roomjid; + roomjid = roomName; + + if (useNicks) { + if (nick) { + roomjid += '/' + nick; + } else { + roomjid += '/' + Strophe.getNodeFromJid(connection.jid); + } + } else { + + var tmpJid = Strophe.getNodeFromJid(connection.jid); + + if(!authenticatedUser) + tmpJid = tmpJid.substr(0, 8); + + roomjid += '/' + tmpJid; + } + connection.emuc.doJoin(roomjid); + }, + myJid: function () { + if(!connection) + return null; + return connection.emuc.myroomjid; + }, + myResource: function () { + if(!connection || ! connection.emuc.myroomjid) + return null; + return Strophe.getResourceFromJid(connection.emuc.myroomjid); + }, + disposeConference: function (onUnload) { + eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload); + var handler = activecall; + if (handler && handler.peerconnection) { + // FIXME: probably removing streams is not required and close() should + // be enough + if (RTC.localAudio) { + handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload); + } + if (RTC.localVideo) { + handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload); + } + handler.peerconnection.close(); + } + activecall = null; + if(!onUnload) + { + this.sessionTerminated = true; + connection.emuc.doLeave(); + } + }, + addListener: function(type, listener) + { + eventEmitter.on(type, listener); + }, + removeListener: function (type, listener) { + eventEmitter.removeListener(type, listener); + }, + allocateConferenceFocus: function(roomName, callback) { + Moderator.allocateConferenceFocus(roomName, callback); + }, + isModerator: function () { + return Moderator.isModerator(); + }, + isSipGatewayEnabled: function () { + return Moderator.isSipGatewayEnabled(); + }, + isExternalAuthEnabled: function () { + return Moderator.isExternalAuthEnabled(); + }, + switchStreams: function (stream, oldStream, callback) { + if (activecall) { + // FIXME: will block switchInProgress on true value in case of exception + activecall.switchStreams(stream, oldStream, callback); + } else { + // We are done immediately + console.error("No conference handler"); + UI.messageHandler.showError('Error', + 'Unable to switch video stream.'); + callback(); + } + }, + setVideoMute: function (mute, callback, options) { + if(activecall && connection && RTC.localVideo) + { + activecall.setVideoMute(mute, callback, options); + } + }, + setAudioMute: function (mute, callback) { + if (!(connection && RTC.localAudio)) { + return false; + } + + + if (this.forceMuted && !mute) { + console.info("Asking focus for unmute"); + connection.moderate.setMute(connection.emuc.myroomjid, mute); + // FIXME: wait for result before resetting muted status + this.forceMuted = false; + } + + if (mute == RTC.localAudio.isMuted()) { + // Nothing to do + return true; + } + + // It is not clear what is the right way to handle multiple tracks. + // So at least make sure that they are all muted or all unmuted and + // that we send presence just once. + RTC.localAudio.mute(); + // isMuted is the opposite of audioEnabled + connection.emuc.addAudioInfoToPresence(mute); + connection.emuc.sendPresence(); + callback(); + return true; + }, + // Really mute video, i.e. dont even send black frames + muteVideo: function (pc, unmute) { + // FIXME: this probably needs another of those lovely state safeguards... + // which checks for iceconn == connected and sigstate == stable + pc.setRemoteDescription(pc.remoteDescription, + function () { + pc.createAnswer( + function (answer) { + var sdp = new SDP(answer.sdp); + if (sdp.media.length > 1) { + if (unmute) + sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv'); + else + sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly'); + sdp.raw = sdp.session + sdp.media.join(''); + answer.sdp = sdp.raw; + } + pc.setLocalDescription(answer, + function () { + console.log('mute SLD ok'); + }, + function (error) { + console.log('mute SLD error'); + UI.messageHandler.showError('Error', + 'Oops! Something went wrong and we failed to ' + + 'mute! (SLD Failure)'); + } + ); + }, + function (error) { + console.log(error); + UI.messageHandler.showError(); + } + ); + }, + function (error) { + console.log('muteVideo SRD error'); + UI.messageHandler.showError('Error', + 'Oops! Something went wrong and we failed to stop video!' + + '(SRD Failure)'); + + } + ); + }, + toggleRecording: function (tokenEmptyCallback, + startingCallback, startedCallback) { + Recording.toggleRecording(tokenEmptyCallback, + startingCallback, startedCallback); + }, + addToPresence: function (name, value, dontSend) { + switch (name) + { + case "displayName": + connection.emuc.addDisplayNameToPresence(value); + break; + case "etherpad": + connection.emuc.addEtherpadToPresence(value); + break; + case "prezi": + connection.emuc.addPreziToPresence(value, 0); + break; + case "preziSlide": + connection.emuc.addCurrentSlideToPresence(value); + break; + case "connectionQuality": + connection.emuc.addConnectionInfoToPresence(value); + break; + case "email": + connection.emuc.addEmailToPresence(value); + default : + console.log("Unknown tag for presence."); + return; + } + if(!dontSend) + connection.emuc.sendPresence(); + }, + sendLogs: function (content) { + // XEP-0337-ish + var message = $msg({to: focusMucJid, type: 'normal'}); + message.c('log', { xmlns: 'urn:xmpp:eventlog', + id: 'PeerConnectionStats'}); + message.c('message').t(content).up(); + if (deflate) { + message.c('tag', {name: "deflated", value: "true"}).up(); + } + message.up(); + + connection.send(message); + }, + populateData: function () { + var data = {}; + if (connection.jingle) { + data = connection.jingle.populateData(); + } + return data; + }, + getLogger: function () { + if(connection.logger) + return connection.logger.log; + return null; + }, + getPrezi: function () { + return connection.emuc.getPrezi(this.myJid()); + }, + removePreziFromPresence: function () { + connection.emuc.removePreziFromPresence(); + connection.emuc.sendPresence(); + }, + sendChatMessage: function (message, nickname) { + connection.emuc.sendMessage(message, nickname); + }, + setSubject: function (topic) { + connection.emuc.setSubject(topic); + }, + lockRoom: function (key, onSuccess, onError, onNotSupported) { + connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported); + }, + dial: function (to, from, roomName,roomPass) { + connection.rayo.dial(to, from, roomName,roomPass); + }, + setMute: function (jid, mute) { + connection.moderate.setMute(jid, mute); + }, + eject: function (jid) { + connection.moderate.eject(jid); + }, + findJidFromResource: function (resource) { + connection.emuc.findJidFromResource(resource); + }, + getMembers: function () { + return connection.emuc.members; + } + +}; + +module.exports = XMPP; +},{"./SDP":2,"./moderator":6,"./recording":7,"./strophe.emuc":8,"./strophe.jingle":9,"./strophe.logger":10,"./strophe.moderate":11,"./strophe.rayo":12,"./strophe.util":13,"events":15}],15:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + throw TypeError('Uncaught, unspecified "error" event.'); + } + return false; + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + var m; + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.listenerCount = function(emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) + ret = 0; + else if (isFunction(emitter._events[type])) + ret = 1; + else + ret = emitter._events[type].length; + return ret; +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}]},{},[14])(14) +}); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi91c3IvbG9jYWwvbGliL25vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL3htcHAvSmluZ2xlU2Vzc2lvbi5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMveG1wcC9TRFAuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL3htcHAvU0RQRGlmZmVyLmpzIiwiL1VzZXJzL2hyaXN0by9Eb2N1bWVudHMvd29ya3NwYWNlL2ppdHNpLW1lZXQvbW9kdWxlcy94bXBwL1NEUFV0aWwuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL3htcHAvVHJhY2VhYmxlUGVlckNvbm5lY3Rpb24uanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL3htcHAvbW9kZXJhdG9yLmpzIiwiL1VzZXJzL2hyaXN0by9Eb2N1bWVudHMvd29ya3NwYWNlL2ppdHNpLW1lZXQvbW9kdWxlcy94bXBwL3JlY29yZGluZy5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMveG1wcC9zdHJvcGhlLmVtdWMuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL3htcHAvc3Ryb3BoZS5qaW5nbGUuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL3htcHAvc3Ryb3BoZS5sb2dnZXIuanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL3htcHAvc3Ryb3BoZS5tb2RlcmF0ZS5qcyIsIi9Vc2Vycy9ocmlzdG8vRG9jdW1lbnRzL3dvcmtzcGFjZS9qaXRzaS1tZWV0L21vZHVsZXMveG1wcC9zdHJvcGhlLnJheW8uanMiLCIvVXNlcnMvaHJpc3RvL0RvY3VtZW50cy93b3Jrc3BhY2Uvaml0c2ktbWVldC9tb2R1bGVzL3htcHAvc3Ryb3BoZS51dGlsLmpzIiwiL1VzZXJzL2hyaXN0by9Eb2N1bWVudHMvd29ya3NwYWNlL2ppdHNpLW1lZXQvbW9kdWxlcy94bXBwL3htcHAuanMiLCIvdXNyL2xvY2FsL2xpYi9ub2RlX21vZHVsZXMvYnJvd3NlcmlmeS9ub2RlX21vZHVsZXMvZXZlbnRzL2V2ZW50cy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1MkNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1bUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwS0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNVZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMxUUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwUEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2SkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMvbEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOVVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDbkJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0ZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzWkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiLyoganNoaW50IC1XMTE3ICovXG52YXIgVHJhY2VhYmxlUGVlckNvbm5lY3Rpb24gPSByZXF1aXJlKFwiLi9UcmFjZWFibGVQZWVyQ29ubmVjdGlvblwiKTtcbnZhciBTRFBEaWZmZXIgPSByZXF1aXJlKFwiLi9TRFBEaWZmZXJcIik7XG52YXIgU0RQVXRpbCA9IHJlcXVpcmUoXCIuL1NEUFV0aWxcIik7XG52YXIgU0RQID0gcmVxdWlyZShcIi4vU0RQXCIpO1xuXG4vLyBKaW5nbGUgc3R1ZmZcbmZ1bmN0aW9uIEppbmdsZVNlc3Npb24obWUsIHNpZCwgY29ubmVjdGlvbiwgc2VydmljZSkge1xuICAgIHRoaXMubWUgPSBtZTtcbiAgICB0aGlzLnNpZCA9IHNpZDtcbiAgICB0aGlzLmNvbm5lY3Rpb24gPSBjb25uZWN0aW9uO1xuICAgIHRoaXMuaW5pdGlhdG9yID0gbnVsbDtcbiAgICB0aGlzLnJlc3BvbmRlciA9IG51bGw7XG4gICAgdGhpcy5pc0luaXRpYXRvciA9IG51bGw7XG4gICAgdGhpcy5wZWVyamlkID0gbnVsbDtcbiAgICB0aGlzLnN0YXRlID0gbnVsbDtcbiAgICB0aGlzLmxvY2FsU0RQID0gbnVsbDtcbiAgICB0aGlzLnJlbW90ZVNEUCA9IG51bGw7XG4gICAgdGhpcy5yZWxheWVkU3RyZWFtcyA9IFtdO1xuICAgIHRoaXMuc3RhcnRUaW1lID0gbnVsbDtcbiAgICB0aGlzLnN0b3BUaW1lID0gbnVsbDtcbiAgICB0aGlzLm1lZGlhX2NvbnN0cmFpbnRzID0gbnVsbDtcbiAgICB0aGlzLnBjX2NvbnN0cmFpbnRzID0gbnVsbDtcbiAgICB0aGlzLmljZV9jb25maWcgPSB7fTtcbiAgICB0aGlzLmRyaXBfY29udGFpbmVyID0gW107XG4gICAgdGhpcy5zZXJ2aWNlID0gc2VydmljZTtcblxuICAgIHRoaXMudXNldHJpY2tsZSA9IHRydWU7XG4gICAgdGhpcy51c2VwcmFuc3dlciA9IGZhbHNlOyAvLyBlYXJseSB0cmFuc3BvcnQgd2FybXVwIC0tIG1pbmQgeW91LCB0aGlzIG1pZ2h0IGZhaWwuIGRlcGVuZHMgb24gd2VicnRjIGlzc3VlIDE3MThcbiAgICB0aGlzLnVzZWRyaXAgPSBmYWxzZTsgLy8gZHJpcHBpbmcgaXMgc2VuZGluZyB0cmlja2xlIGNhbmRpZGF0ZXMgbm90IG9uZS1ieS1vbmVcblxuICAgIHRoaXMuaGFkc3R1bmNhbmRpZGF0ZSA9IGZhbHNlO1xuICAgIHRoaXMuaGFkdHVybmNhbmRpZGF0ZSA9IGZhbHNlO1xuICAgIHRoaXMubGFzdGljZWNhbmRpZGF0ZSA9IGZhbHNlO1xuXG4gICAgdGhpcy5zdGF0c2ludGVydmFsID0gbnVsbDtcblxuICAgIHRoaXMucmVhc29uID0gbnVsbDtcblxuICAgIHRoaXMuYWRkc3NyYyA9IFtdO1xuICAgIHRoaXMucmVtb3Zlc3NyYyA9IFtdO1xuICAgIHRoaXMucGVuZGluZ29wID0gbnVsbDtcbiAgICB0aGlzLnN3aXRjaHN0cmVhbXMgPSBmYWxzZTtcblxuICAgIHRoaXMud2FpdCA9IHRydWU7XG4gICAgdGhpcy5sb2NhbFN0cmVhbXNTU1JDID0gbnVsbDtcblxuICAgIC8qKlxuICAgICAqIFRoZSBpbmRpY2F0b3Igd2hpY2ggZGV0ZXJtaW5lcyB3aGV0aGVyIHRoZSAobG9jYWwpIHZpZGVvIGhhcyBiZWVuIG11dGVkXG4gICAgICogaW4gcmVzcG9uc2UgdG8gYSB1c2VyIGNvbW1hbmQgaW4gY29udHJhc3QgdG8gYW4gYXV0b21hdGljIGRlY2lzaW9uIG1hZGVcbiAgICAgKiBieSB0aGUgYXBwbGljYXRpb24gbG9naWMuXG4gICAgICovXG4gICAgdGhpcy52aWRlb011dGVCeVVzZXIgPSBmYWxzZTtcbn1cblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuaW5pdGlhdGUgPSBmdW5jdGlvbiAocGVlcmppZCwgaXNJbml0aWF0b3IpIHtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgaWYgKHRoaXMuc3RhdGUgIT09IG51bGwpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcignYXR0ZW1wdCB0byBpbml0aWF0ZSBvbiBzZXNzaW9uICcgKyB0aGlzLnNpZCArXG4gICAgICAgICAgICAnaW4gc3RhdGUgJyArIHRoaXMuc3RhdGUpO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuaXNJbml0aWF0b3IgPSBpc0luaXRpYXRvcjtcbiAgICB0aGlzLnN0YXRlID0gJ3BlbmRpbmcnO1xuICAgIHRoaXMuaW5pdGlhdG9yID0gaXNJbml0aWF0b3IgPyB0aGlzLm1lIDogcGVlcmppZDtcbiAgICB0aGlzLnJlc3BvbmRlciA9ICFpc0luaXRpYXRvciA/IHRoaXMubWUgOiBwZWVyamlkO1xuICAgIHRoaXMucGVlcmppZCA9IHBlZXJqaWQ7XG4gICAgdGhpcy5oYWRzdHVuY2FuZGlkYXRlID0gZmFsc2U7XG4gICAgdGhpcy5oYWR0dXJuY2FuZGlkYXRlID0gZmFsc2U7XG4gICAgdGhpcy5sYXN0aWNlY2FuZGlkYXRlID0gZmFsc2U7XG5cbiAgICB0aGlzLnBlZXJjb25uZWN0aW9uXG4gICAgICAgID0gbmV3IFRyYWNlYWJsZVBlZXJDb25uZWN0aW9uKFxuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLmppbmdsZS5pY2VfY29uZmlnLFxuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLmppbmdsZS5wY19jb25zdHJhaW50cyApO1xuXG4gICAgdGhpcy5wZWVyY29ubmVjdGlvbi5vbmljZWNhbmRpZGF0ZSA9IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBzZWxmLnNlbmRJY2VDYW5kaWRhdGUoZXZlbnQuY2FuZGlkYXRlKTtcbiAgICB9O1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24ub25hZGRzdHJlYW0gPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgY29uc29sZS5sb2coXCJSRU1PVEUgU1RSRUFNIEFEREVEOiBcIiArIGV2ZW50LnN0cmVhbSArIFwiIC0gXCIgKyBldmVudC5zdHJlYW0uaWQpO1xuICAgICAgICBzZWxmLnJlbW90ZVN0cmVhbUFkZGVkKGV2ZW50KTtcbiAgICB9O1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24ub25yZW1vdmVzdHJlYW0gPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgLy8gUmVtb3ZlIHRoZSBzdHJlYW0gZnJvbSByZW1vdGVTdHJlYW1zXG4gICAgICAgIC8vIEZJWE1FOiByZW1vdGVzdHJlYW1yZW1vdmVkLmppbmdsZSBub3QgZGVmaW5lZCBhbnl3aGVyZSh1bnVzZWQpXG4gICAgICAgICQoZG9jdW1lbnQpLnRyaWdnZXIoJ3JlbW90ZXN0cmVhbXJlbW92ZWQuamluZ2xlJywgW2V2ZW50LCBzZWxmLnNpZF0pO1xuICAgIH07XG4gICAgdGhpcy5wZWVyY29ubmVjdGlvbi5vbnNpZ25hbGluZ3N0YXRlY2hhbmdlID0gZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIGlmICghKHNlbGYgJiYgc2VsZi5wZWVyY29ubmVjdGlvbikpIHJldHVybjtcbiAgICB9O1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24ub25pY2Vjb25uZWN0aW9uc3RhdGVjaGFuZ2UgPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgaWYgKCEoc2VsZiAmJiBzZWxmLnBlZXJjb25uZWN0aW9uKSkgcmV0dXJuO1xuICAgICAgICBzd2l0Y2ggKHNlbGYucGVlcmNvbm5lY3Rpb24uaWNlQ29ubmVjdGlvblN0YXRlKSB7XG4gICAgICAgICAgICBjYXNlICdjb25uZWN0ZWQnOlxuICAgICAgICAgICAgICAgIHRoaXMuc3RhcnRUaW1lID0gbmV3IERhdGUoKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIGNhc2UgJ2Rpc2Nvbm5lY3RlZCc6XG4gICAgICAgICAgICAgICAgdGhpcy5zdG9wVGltZSA9IG5ldyBEYXRlKCk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgb25JY2VDb25uZWN0aW9uU3RhdGVDaGFuZ2Uoc2VsZi5zaWQsIHNlbGYpO1xuICAgIH07XG4gICAgLy8gYWRkIGFueSBsb2NhbCBhbmQgcmVsYXllZCBzdHJlYW1cbiAgICBSVEMubG9jYWxTdHJlYW1zLmZvckVhY2goZnVuY3Rpb24oc3RyZWFtKSB7XG4gICAgICAgIHNlbGYucGVlcmNvbm5lY3Rpb24uYWRkU3RyZWFtKHN0cmVhbS5nZXRPcmlnaW5hbFN0cmVhbSgpKTtcbiAgICB9KTtcbiAgICB0aGlzLnJlbGF5ZWRTdHJlYW1zLmZvckVhY2goZnVuY3Rpb24oc3RyZWFtKSB7XG4gICAgICAgIHNlbGYucGVlcmNvbm5lY3Rpb24uYWRkU3RyZWFtKHN0cmVhbSk7XG4gICAgfSk7XG59O1xuXG5mdW5jdGlvbiBvbkljZUNvbm5lY3Rpb25TdGF0ZUNoYW5nZShzaWQsIHNlc3Npb24pIHtcbiAgICBzd2l0Y2ggKHNlc3Npb24ucGVlcmNvbm5lY3Rpb24uaWNlQ29ubmVjdGlvblN0YXRlKSB7XG4gICAgICAgIGNhc2UgJ2NoZWNraW5nJzpcbiAgICAgICAgICAgIHNlc3Npb24udGltZUNoZWNraW5nID0gKG5ldyBEYXRlKCkpLmdldFRpbWUoKTtcbiAgICAgICAgICAgIHNlc3Npb24uZmlyc3Rjb25uZWN0ID0gdHJ1ZTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlICdjb21wbGV0ZWQnOiAvLyBvbiBjYWxsZXIgc2lkZVxuICAgICAgICBjYXNlICdjb25uZWN0ZWQnOlxuICAgICAgICAgICAgaWYgKHNlc3Npb24uZmlyc3Rjb25uZWN0KSB7XG4gICAgICAgICAgICAgICAgc2Vzc2lvbi5maXJzdGNvbm5lY3QgPSBmYWxzZTtcbiAgICAgICAgICAgICAgICB2YXIgbWV0YWRhdGEgPSB7fTtcbiAgICAgICAgICAgICAgICBtZXRhZGF0YS5zZXR1cFRpbWVcbiAgICAgICAgICAgICAgICAgICAgPSAobmV3IERhdGUoKSkuZ2V0VGltZSgpIC0gc2Vzc2lvbi50aW1lQ2hlY2tpbmc7XG4gICAgICAgICAgICAgICAgc2Vzc2lvbi5wZWVyY29ubmVjdGlvbi5nZXRTdGF0cyhmdW5jdGlvbiAocmVzKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmKHJlcyAmJiByZXMucmVzdWx0KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXMucmVzdWx0KCkuZm9yRWFjaChmdW5jdGlvbiAocmVwb3J0KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHJlcG9ydC50eXBlID09ICdnb29nQ2FuZGlkYXRlUGFpcicgJiZcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwb3J0LnN0YXQoJ2dvb2dBY3RpdmVDb25uZWN0aW9uJykgPT0gJ3RydWUnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGFkYXRhLmxvY2FsQ2FuZGlkYXRlVHlwZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPSByZXBvcnQuc3RhdCgnZ29vZ0xvY2FsQ2FuZGlkYXRlVHlwZScpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRhZGF0YS5yZW1vdGVDYW5kaWRhdGVUeXBlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA9IHJlcG9ydC5zdGF0KCdnb29nUmVtb3RlQ2FuZGlkYXRlVHlwZScpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIGxvZyBwYWlyIGFzIHdlbGwgc28gd2UgY2FuIGdldCBuaWNlIHBpZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBjaGFydHNcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0YWRhdGEuY2FuZGlkYXRlUGFpclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPSByZXBvcnQuc3RhdCgnZ29vZ0xvY2FsQ2FuZGlkYXRlVHlwZScpICtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnOycgK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcG9ydC5zdGF0KCdnb29nUmVtb3RlQ2FuZGlkYXRlVHlwZScpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChyZXBvcnQuc3RhdCgnZ29vZ1JlbW90ZUFkZHJlc3MnKS5pbmRleE9mKCdbJykgPT09IDApXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGFkYXRhLmlwdjYgPSB0cnVlO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGJyZWFrO1xuICAgIH1cbn1cblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuYWNjZXB0ID0gZnVuY3Rpb24gKCkge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB0aGlzLnN0YXRlID0gJ2FjdGl2ZSc7XG5cbiAgICB2YXIgcHJhbnN3ZXIgPSB0aGlzLnBlZXJjb25uZWN0aW9uLmxvY2FsRGVzY3JpcHRpb247XG4gICAgaWYgKCFwcmFuc3dlciB8fCBwcmFuc3dlci50eXBlICE9ICdwcmFuc3dlcicpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zb2xlLmxvZygnZ29pbmcgZnJvbSBwcmFuc3dlciB0byBhbnN3ZXInKTtcbiAgICBpZiAodGhpcy51c2V0cmlja2xlKSB7XG4gICAgICAgIC8vIHJlbW92ZSBjYW5kaWRhdGVzIGFscmVhZHkgc2VudCBmcm9tIHNlc3Npb24tYWNjZXB0XG4gICAgICAgIHZhciBsaW5lcyA9IFNEUFV0aWwuZmluZF9saW5lcyhwcmFuc3dlci5zZHAsICdhPWNhbmRpZGF0ZTonKTtcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBsaW5lcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgcHJhbnN3ZXIuc2RwID0gcHJhbnN3ZXIuc2RwLnJlcGxhY2UobGluZXNbaV0gKyAnXFxyXFxuJywgJycpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHdoaWxlIChTRFBVdGlsLmZpbmRfbGluZShwcmFuc3dlci5zZHAsICdhPWluYWN0aXZlJykpIHtcbiAgICAgICAgLy8gRklYTUU6IGNoYW5nZSBhbnkgaW5hY3RpdmUgdG8gc2VuZHJlY3Ygb3Igd2hhdGV2ZXIgdGhleSB3ZXJlIG9yaWdpbmFsbHlcbiAgICAgICAgcHJhbnN3ZXIuc2RwID0gcHJhbnN3ZXIuc2RwLnJlcGxhY2UoJ2E9aW5hY3RpdmUnLCAnYT1zZW5kcmVjdicpO1xuICAgIH1cbiAgICBwcmFuc3dlciA9IHNpbXVsY2FzdC5yZXZlcnNlVHJhbnNmb3JtTG9jYWxEZXNjcmlwdGlvbihwcmFuc3dlcik7XG4gICAgdmFyIHByc2RwID0gbmV3IFNEUChwcmFuc3dlci5zZHApO1xuICAgIHZhciBhY2NlcHQgPSAkaXEoe3RvOiB0aGlzLnBlZXJqaWQsXG4gICAgICAgIHR5cGU6ICdzZXQnfSlcbiAgICAgICAgLmMoJ2ppbmdsZScsIHt4bWxuczogJ3Vybjp4bXBwOmppbmdsZToxJyxcbiAgICAgICAgICAgIGFjdGlvbjogJ3Nlc3Npb24tYWNjZXB0JyxcbiAgICAgICAgICAgIGluaXRpYXRvcjogdGhpcy5pbml0aWF0b3IsXG4gICAgICAgICAgICByZXNwb25kZXI6IHRoaXMucmVzcG9uZGVyLFxuICAgICAgICAgICAgc2lkOiB0aGlzLnNpZCB9KTtcbiAgICBwcnNkcC50b0ppbmdsZShhY2NlcHQsIHRoaXMuaW5pdGlhdG9yID09IHRoaXMubWUgPyAnaW5pdGlhdG9yJyA6ICdyZXNwb25kZXInLCB0aGlzLmxvY2FsU3RyZWFtc1NTUkMpO1xuICAgIHZhciBzZHAgPSB0aGlzLnBlZXJjb25uZWN0aW9uLmxvY2FsRGVzY3JpcHRpb24uc2RwO1xuICAgIHdoaWxlIChTRFBVdGlsLmZpbmRfbGluZShzZHAsICdhPWluYWN0aXZlJykpIHtcbiAgICAgICAgLy8gRklYTUU6IGNoYW5nZSBhbnkgaW5hY3RpdmUgdG8gc2VuZHJlY3Ygb3Igd2hhdGV2ZXIgdGhleSB3ZXJlIG9yaWdpbmFsbHlcbiAgICAgICAgc2RwID0gc2RwLnJlcGxhY2UoJ2E9aW5hY3RpdmUnLCAnYT1zZW5kcmVjdicpO1xuICAgIH1cbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgdGhpcy5wZWVyY29ubmVjdGlvbi5zZXRMb2NhbERlc2NyaXB0aW9uKG5ldyBSVENTZXNzaW9uRGVzY3JpcHRpb24oe3R5cGU6ICdhbnN3ZXInLCBzZHA6IHNkcH0pLFxuICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAvL2NvbnNvbGUubG9nKCdzZXRMb2NhbERlc2NyaXB0aW9uIHN1Y2Nlc3MnKTtcbiAgICAgICAgICAgIHNlbGYuc2V0TG9jYWxEZXNjcmlwdGlvbigpO1xuXG4gICAgICAgICAgICBzZWxmLmNvbm5lY3Rpb24uc2VuZElRKGFjY2VwdCxcbiAgICAgICAgICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBhY2sgPSB7fTtcbiAgICAgICAgICAgICAgICAgICAgYWNrLnNvdXJjZSA9ICdhbnN3ZXInO1xuICAgICAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdhY2suamluZ2xlJywgW3NlbGYuc2lkLCBhY2tdKTtcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChzdGFuemEpIHtcbiAgICAgICAgICAgICAgICAgICAgdmFyIGVycm9yID0gKCQoc3RhbnphKS5maW5kKCdlcnJvcicpLmxlbmd0aCkgPyB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb2RlOiAkKHN0YW56YSkuZmluZCgnZXJyb3InKS5hdHRyKCdjb2RlJyksXG4gICAgICAgICAgICAgICAgICAgICAgICByZWFzb246ICQoc3RhbnphKS5maW5kKCdlcnJvciA6Zmlyc3QnKVswXS50YWdOYW1lXG4gICAgICAgICAgICAgICAgICAgIH06e307XG4gICAgICAgICAgICAgICAgICAgIGVycm9yLnNvdXJjZSA9ICdhbnN3ZXInO1xuICAgICAgICAgICAgICAgICAgICBKaW5nbGVTZXNzaW9uLm9uSmluZ2xlRXJyb3Ioc2VsZi5zaWQsIGVycm9yKTtcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIDEwMDAwKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24gKGUpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ3NldExvY2FsRGVzY3JpcHRpb24gZmFpbGVkJywgZSk7XG4gICAgICAgIH1cbiAgICApO1xufTtcblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUudGVybWluYXRlID0gZnVuY3Rpb24gKHJlYXNvbikge1xuICAgIHRoaXMuc3RhdGUgPSAnZW5kZWQnO1xuICAgIHRoaXMucmVhc29uID0gcmVhc29uO1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24uY2xvc2UoKTtcbiAgICBpZiAodGhpcy5zdGF0c2ludGVydmFsICE9PSBudWxsKSB7XG4gICAgICAgIHdpbmRvdy5jbGVhckludGVydmFsKHRoaXMuc3RhdHNpbnRlcnZhbCk7XG4gICAgICAgIHRoaXMuc3RhdHNpbnRlcnZhbCA9IG51bGw7XG4gICAgfVxufTtcblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuYWN0aXZlID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiB0aGlzLnN0YXRlID09ICdhY3RpdmUnO1xufTtcblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuc2VuZEljZUNhbmRpZGF0ZSA9IGZ1bmN0aW9uIChjYW5kaWRhdGUpIHtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgaWYgKGNhbmRpZGF0ZSAmJiAhdGhpcy5sYXN0aWNlY2FuZGlkYXRlKSB7XG4gICAgICAgIHZhciBpY2UgPSBTRFBVdGlsLmljZXBhcmFtcyh0aGlzLmxvY2FsU0RQLm1lZGlhW2NhbmRpZGF0ZS5zZHBNTGluZUluZGV4XSwgdGhpcy5sb2NhbFNEUC5zZXNzaW9uKTtcbiAgICAgICAgdmFyIGpjYW5kID0gU0RQVXRpbC5jYW5kaWRhdGVUb0ppbmdsZShjYW5kaWRhdGUuY2FuZGlkYXRlKTtcbiAgICAgICAgaWYgKCEoaWNlICYmIGpjYW5kKSkge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcignZmFpbGVkIHRvIGdldCBpY2UgJiYgamNhbmQnKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBpY2UueG1sbnMgPSAndXJuOnhtcHA6amluZ2xlOnRyYW5zcG9ydHM6aWNlLXVkcDoxJztcblxuICAgICAgICBpZiAoamNhbmQudHlwZSA9PT0gJ3NyZmx4Jykge1xuICAgICAgICAgICAgdGhpcy5oYWRzdHVuY2FuZGlkYXRlID0gdHJ1ZTtcbiAgICAgICAgfSBlbHNlIGlmIChqY2FuZC50eXBlID09PSAncmVsYXknKSB7XG4gICAgICAgICAgICB0aGlzLmhhZHR1cm5jYW5kaWRhdGUgPSB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMudXNldHJpY2tsZSkge1xuICAgICAgICAgICAgaWYgKHRoaXMudXNlZHJpcCkge1xuICAgICAgICAgICAgICAgIGlmICh0aGlzLmRyaXBfY29udGFpbmVyLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgICAgICAgICAvLyBzdGFydCAyMG1zIGNhbGxvdXRcbiAgICAgICAgICAgICAgICAgICAgd2luZG93LnNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHNlbGYuZHJpcF9jb250YWluZXIubGVuZ3RoID09PSAwKSByZXR1cm47XG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxmLnNlbmRJY2VDYW5kaWRhdGVzKHNlbGYuZHJpcF9jb250YWluZXIpO1xuICAgICAgICAgICAgICAgICAgICAgICAgc2VsZi5kcmlwX2NvbnRhaW5lciA9IFtdO1xuICAgICAgICAgICAgICAgICAgICB9LCAyMCk7XG5cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgdGhpcy5kcmlwX2NvbnRhaW5lci5wdXNoKGNhbmRpZGF0ZSk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBzZWxmLnNlbmRJY2VDYW5kaWRhdGUoW2NhbmRpZGF0ZV0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgICAgLy9jb25zb2xlLmxvZygnc2VuZEljZUNhbmRpZGF0ZTogbGFzdCBjYW5kaWRhdGUuJyk7XG4gICAgICAgIGlmICghdGhpcy51c2V0cmlja2xlKSB7XG4gICAgICAgICAgICAvL2NvbnNvbGUubG9nKCdzaG91bGQgc2VuZCBmdWxsIG9mZmVyIG5vdy4uLicpO1xuICAgICAgICAgICAgdmFyIGluaXQgPSAkaXEoe3RvOiB0aGlzLnBlZXJqaWQsXG4gICAgICAgICAgICAgICAgdHlwZTogJ3NldCd9KVxuICAgICAgICAgICAgICAgIC5jKCdqaW5nbGUnLCB7eG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6MScsXG4gICAgICAgICAgICAgICAgICAgIGFjdGlvbjogdGhpcy5wZWVyY29ubmVjdGlvbi5sb2NhbERlc2NyaXB0aW9uLnR5cGUgPT0gJ29mZmVyJyA/ICdzZXNzaW9uLWluaXRpYXRlJyA6ICdzZXNzaW9uLWFjY2VwdCcsXG4gICAgICAgICAgICAgICAgICAgIGluaXRpYXRvcjogdGhpcy5pbml0aWF0b3IsXG4gICAgICAgICAgICAgICAgICAgIHNpZDogdGhpcy5zaWR9KTtcbiAgICAgICAgICAgIHRoaXMubG9jYWxTRFAgPSBuZXcgU0RQKHRoaXMucGVlcmNvbm5lY3Rpb24ubG9jYWxEZXNjcmlwdGlvbi5zZHApO1xuICAgICAgICAgICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgICAgICAgICAgdmFyIHNlbmRKaW5nbGUgPSBmdW5jdGlvbiAoc3NyYykge1xuICAgICAgICAgICAgICAgIGlmKCFzc3JjKVxuICAgICAgICAgICAgICAgICAgICBzc3JjID0ge307XG4gICAgICAgICAgICAgICAgc2VsZi5sb2NhbFNEUC50b0ppbmdsZShpbml0LCBzZWxmLmluaXRpYXRvciA9PSBzZWxmLm1lID8gJ2luaXRpYXRvcicgOiAncmVzcG9uZGVyJywgc3NyYyk7XG4gICAgICAgICAgICAgICAgc2VsZi5jb25uZWN0aW9uLnNlbmRJUShpbml0LFxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAvL2NvbnNvbGUubG9nKCdzZXNzaW9uIGluaXRpYXRlIGFjaycpO1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGFjayA9IHt9O1xuICAgICAgICAgICAgICAgICAgICAgICAgYWNrLnNvdXJjZSA9ICdvZmZlcic7XG4gICAgICAgICAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdhY2suamluZ2xlJywgW3NlbGYuc2lkLCBhY2tdKTtcbiAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gKHN0YW56YSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgc2VsZi5zdGF0ZSA9ICdlcnJvcic7XG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxmLnBlZXJjb25uZWN0aW9uLmNsb3NlKCk7XG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgZXJyb3IgPSAoJChzdGFuemEpLmZpbmQoJ2Vycm9yJykubGVuZ3RoKSA/IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2RlOiAkKHN0YW56YSkuZmluZCgnZXJyb3InKS5hdHRyKCdjb2RlJyksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVhc29uOiAkKHN0YW56YSkuZmluZCgnZXJyb3IgOmZpcnN0JylbMF0udGFnTmFtZSxcbiAgICAgICAgICAgICAgICAgICAgICAgIH06e307XG4gICAgICAgICAgICAgICAgICAgICAgICBlcnJvci5zb3VyY2UgPSAnb2ZmZXInO1xuICAgICAgICAgICAgICAgICAgICAgICAgSmluZ2xlU2Vzc2lvbi5vbkppbmdsZUVycm9yKHNlbGYuc2lkLCBlcnJvcik7XG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgIDEwMDAwKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHNlbmRKaW5nbGUoKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLmxhc3RpY2VjYW5kaWRhdGUgPSB0cnVlO1xuICAgICAgICBjb25zb2xlLmxvZygnSGF2ZSB3ZSBlbmNvdW50ZXJlZCBhbnkgc3JmbHggY2FuZGlkYXRlcz8gJyArIHRoaXMuaGFkc3R1bmNhbmRpZGF0ZSk7XG4gICAgICAgIGNvbnNvbGUubG9nKCdIYXZlIHdlIGVuY291bnRlcmVkIGFueSByZWxheSBjYW5kaWRhdGVzPyAnICsgdGhpcy5oYWR0dXJuY2FuZGlkYXRlKTtcblxuICAgICAgICBpZiAoISh0aGlzLmhhZHN0dW5jYW5kaWRhdGUgfHwgdGhpcy5oYWR0dXJuY2FuZGlkYXRlKSAmJiB0aGlzLnBlZXJjb25uZWN0aW9uLnNpZ25hbGluZ1N0YXRlICE9ICdjbG9zZWQnKSB7XG4gICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdub3N0dW5jYW5kaWRhdGVzLmppbmdsZScsIFt0aGlzLnNpZF0pO1xuICAgICAgICB9XG4gICAgfVxufTtcblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuc2VuZEljZUNhbmRpZGF0ZXMgPSBmdW5jdGlvbiAoY2FuZGlkYXRlcykge1xuICAgIGNvbnNvbGUubG9nKCdzZW5kSWNlQ2FuZGlkYXRlcycsIGNhbmRpZGF0ZXMpO1xuICAgIHZhciBjYW5kID0gJGlxKHt0bzogdGhpcy5wZWVyamlkLCB0eXBlOiAnc2V0J30pXG4gICAgICAgIC5jKCdqaW5nbGUnLCB7eG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6MScsXG4gICAgICAgICAgICBhY3Rpb246ICd0cmFuc3BvcnQtaW5mbycsXG4gICAgICAgICAgICBpbml0aWF0b3I6IHRoaXMuaW5pdGlhdG9yLFxuICAgICAgICAgICAgc2lkOiB0aGlzLnNpZH0pO1xuICAgIGZvciAodmFyIG1pZCA9IDA7IG1pZCA8IHRoaXMubG9jYWxTRFAubWVkaWEubGVuZ3RoOyBtaWQrKykge1xuICAgICAgICB2YXIgY2FuZHMgPSBjYW5kaWRhdGVzLmZpbHRlcihmdW5jdGlvbiAoZWwpIHsgcmV0dXJuIGVsLnNkcE1MaW5lSW5kZXggPT0gbWlkOyB9KTtcbiAgICAgICAgdmFyIG1saW5lID0gU0RQVXRpbC5wYXJzZV9tbGluZSh0aGlzLmxvY2FsU0RQLm1lZGlhW21pZF0uc3BsaXQoJ1xcclxcbicpWzBdKTtcbiAgICAgICAgaWYgKGNhbmRzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIHZhciBpY2UgPSBTRFBVdGlsLmljZXBhcmFtcyh0aGlzLmxvY2FsU0RQLm1lZGlhW21pZF0sIHRoaXMubG9jYWxTRFAuc2Vzc2lvbik7XG4gICAgICAgICAgICBpY2UueG1sbnMgPSAndXJuOnhtcHA6amluZ2xlOnRyYW5zcG9ydHM6aWNlLXVkcDoxJztcbiAgICAgICAgICAgIGNhbmQuYygnY29udGVudCcsIHtjcmVhdG9yOiB0aGlzLmluaXRpYXRvciA9PSB0aGlzLm1lID8gJ2luaXRpYXRvcicgOiAncmVzcG9uZGVyJyxcbiAgICAgICAgICAgICAgICBuYW1lOiAoY2FuZHNbMF0uc2RwTWlkPyBjYW5kc1swXS5zZHBNaWQgOiBtbGluZS5tZWRpYSlcbiAgICAgICAgICAgIH0pLmMoJ3RyYW5zcG9ydCcsIGljZSk7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGNhbmRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgY2FuZC5jKCdjYW5kaWRhdGUnLCBTRFBVdGlsLmNhbmRpZGF0ZVRvSmluZ2xlKGNhbmRzW2ldLmNhbmRpZGF0ZSkpLnVwKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBhZGQgZmluZ2VycHJpbnRcbiAgICAgICAgICAgIGlmIChTRFBVdGlsLmZpbmRfbGluZSh0aGlzLmxvY2FsU0RQLm1lZGlhW21pZF0sICdhPWZpbmdlcnByaW50OicsIHRoaXMubG9jYWxTRFAuc2Vzc2lvbikpIHtcbiAgICAgICAgICAgICAgICB2YXIgdG1wID0gU0RQVXRpbC5wYXJzZV9maW5nZXJwcmludChTRFBVdGlsLmZpbmRfbGluZSh0aGlzLmxvY2FsU0RQLm1lZGlhW21pZF0sICdhPWZpbmdlcnByaW50OicsIHRoaXMubG9jYWxTRFAuc2Vzc2lvbikpO1xuICAgICAgICAgICAgICAgIHRtcC5yZXF1aXJlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgY2FuZC5jKFxuICAgICAgICAgICAgICAgICAgICAnZmluZ2VycHJpbnQnLFxuICAgICAgICAgICAgICAgICAgICB7eG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6YXBwczpkdGxzOjAnfSlcbiAgICAgICAgICAgICAgICAgICAgLnQodG1wLmZpbmdlcnByaW50KTtcbiAgICAgICAgICAgICAgICBkZWxldGUgdG1wLmZpbmdlcnByaW50O1xuICAgICAgICAgICAgICAgIGNhbmQuYXR0cnModG1wKTtcbiAgICAgICAgICAgICAgICBjYW5kLnVwKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjYW5kLnVwKCk7IC8vIHRyYW5zcG9ydFxuICAgICAgICAgICAgY2FuZC51cCgpOyAvLyBjb250ZW50XG4gICAgICAgIH1cbiAgICB9XG4gICAgLy8gbWlnaHQgbWVyZ2UgbGFzdC1jYW5kaWRhdGUgbm90aWZpY2F0aW9uIGludG8gdGhpcywgYnV0IGl0IGlzIGNhbGxlZCBhbG90IGxhdGVyLiBTZWUgd2VicnRjIGlzc3VlICMyMzQwXG4gICAgLy9jb25zb2xlLmxvZygnd2FzIHRoaXMgdGhlIGxhc3QgY2FuZGlkYXRlJywgdGhpcy5sYXN0aWNlY2FuZGlkYXRlKTtcbiAgICB0aGlzLmNvbm5lY3Rpb24uc2VuZElRKGNhbmQsXG4gICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHZhciBhY2sgPSB7fTtcbiAgICAgICAgICAgIGFjay5zb3VyY2UgPSAndHJhbnNwb3J0aW5mbyc7XG4gICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdhY2suamluZ2xlJywgW3RoaXMuc2lkLCBhY2tdKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24gKHN0YW56YSkge1xuICAgICAgICAgICAgdmFyIGVycm9yID0gKCQoc3RhbnphKS5maW5kKCdlcnJvcicpLmxlbmd0aCkgPyB7XG4gICAgICAgICAgICAgICAgY29kZTogJChzdGFuemEpLmZpbmQoJ2Vycm9yJykuYXR0cignY29kZScpLFxuICAgICAgICAgICAgICAgIHJlYXNvbjogJChzdGFuemEpLmZpbmQoJ2Vycm9yIDpmaXJzdCcpWzBdLnRhZ05hbWUsXG4gICAgICAgICAgICB9Ont9O1xuICAgICAgICAgICAgZXJyb3Iuc291cmNlID0gJ3RyYW5zcG9ydGluZm8nO1xuICAgICAgICAgICAgSmluZ2xlU2Vzc2lvbi5vbkppbmdsZUVycm9yKHRoaXMuc2lkLCBlcnJvcik7XG4gICAgICAgIH0sXG4gICAgICAgIDEwMDAwKTtcbn07XG5cblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuc2VuZE9mZmVyID0gZnVuY3Rpb24gKCkge1xuICAgIC8vY29uc29sZS5sb2coJ3NlbmRPZmZlci4uLicpO1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB0aGlzLnBlZXJjb25uZWN0aW9uLmNyZWF0ZU9mZmVyKGZ1bmN0aW9uIChzZHApIHtcbiAgICAgICAgICAgIHNlbGYuY3JlYXRlZE9mZmVyKHNkcCk7XG4gICAgICAgIH0sXG4gICAgICAgIGZ1bmN0aW9uIChlKSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKCdjcmVhdGVPZmZlciBmYWlsZWQnLCBlKTtcbiAgICAgICAgfSxcbiAgICAgICAgdGhpcy5tZWRpYV9jb25zdHJhaW50c1xuICAgICk7XG59O1xuXG5KaW5nbGVTZXNzaW9uLnByb3RvdHlwZS5jcmVhdGVkT2ZmZXIgPSBmdW5jdGlvbiAoc2RwKSB7XG4gICAgLy9jb25zb2xlLmxvZygnY3JlYXRlZE9mZmVyJywgc2RwKTtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgdGhpcy5sb2NhbFNEUCA9IG5ldyBTRFAoc2RwLnNkcCk7XG4gICAgLy90aGlzLmxvY2FsU0RQLm1hbmdsZSgpO1xuICAgIHZhciBzZW5kSmluZ2xlID0gZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgaW5pdCA9ICRpcSh7dG86IHRoaXMucGVlcmppZCxcbiAgICAgICAgICAgIHR5cGU6ICdzZXQnfSlcbiAgICAgICAgICAgIC5jKCdqaW5nbGUnLCB7eG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6MScsXG4gICAgICAgICAgICAgICAgYWN0aW9uOiAnc2Vzc2lvbi1pbml0aWF0ZScsXG4gICAgICAgICAgICAgICAgaW5pdGlhdG9yOiB0aGlzLmluaXRpYXRvcixcbiAgICAgICAgICAgICAgICBzaWQ6IHRoaXMuc2lkfSk7XG4gICAgICAgIHNlbGYubG9jYWxTRFAudG9KaW5nbGUoaW5pdCwgdGhpcy5pbml0aWF0b3IgPT0gdGhpcy5tZSA/ICdpbml0aWF0b3InIDogJ3Jlc3BvbmRlcicsIHRoaXMubG9jYWxTdHJlYW1zU1NSQyk7XG4gICAgICAgIHNlbGYuY29ubmVjdGlvbi5zZW5kSVEoaW5pdCxcbiAgICAgICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICB2YXIgYWNrID0ge307XG4gICAgICAgICAgICAgICAgYWNrLnNvdXJjZSA9ICdvZmZlcic7XG4gICAgICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcignYWNrLmppbmdsZScsIFtzZWxmLnNpZCwgYWNrXSk7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgZnVuY3Rpb24gKHN0YW56YSkge1xuICAgICAgICAgICAgICAgIHNlbGYuc3RhdGUgPSAnZXJyb3InO1xuICAgICAgICAgICAgICAgIHNlbGYucGVlcmNvbm5lY3Rpb24uY2xvc2UoKTtcbiAgICAgICAgICAgICAgICB2YXIgZXJyb3IgPSAoJChzdGFuemEpLmZpbmQoJ2Vycm9yJykubGVuZ3RoKSA/IHtcbiAgICAgICAgICAgICAgICAgICAgY29kZTogJChzdGFuemEpLmZpbmQoJ2Vycm9yJykuYXR0cignY29kZScpLFxuICAgICAgICAgICAgICAgICAgICByZWFzb246ICQoc3RhbnphKS5maW5kKCdlcnJvciA6Zmlyc3QnKVswXS50YWdOYW1lLFxuICAgICAgICAgICAgICAgIH06e307XG4gICAgICAgICAgICAgICAgZXJyb3Iuc291cmNlID0gJ29mZmVyJztcbiAgICAgICAgICAgICAgICBKaW5nbGVTZXNzaW9uLm9uSmluZ2xlRXJyb3Ioc2VsZi5zaWQsIGVycm9yKTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAxMDAwMCk7XG4gICAgfVxuICAgIHNkcC5zZHAgPSB0aGlzLmxvY2FsU0RQLnJhdztcbiAgICB0aGlzLnBlZXJjb25uZWN0aW9uLnNldExvY2FsRGVzY3JpcHRpb24oc2RwLFxuICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBpZihzZWxmLnVzZXRyaWNrbGUpXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgc2VuZEppbmdsZSgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgc2VsZi5zZXRMb2NhbERlc2NyaXB0aW9uKCk7XG4gICAgICAgICAgICAvL2NvbnNvbGUubG9nKCdzZXRMb2NhbERlc2NyaXB0aW9uIHN1Y2Nlc3MnKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24gKGUpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ3NldExvY2FsRGVzY3JpcHRpb24gZmFpbGVkJywgZSk7XG4gICAgICAgIH1cbiAgICApO1xuICAgIHZhciBjYW5kcyA9IFNEUFV0aWwuZmluZF9saW5lcyh0aGlzLmxvY2FsU0RQLnJhdywgJ2E9Y2FuZGlkYXRlOicpO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgY2FuZHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIGNhbmQgPSBTRFBVdGlsLnBhcnNlX2ljZWNhbmRpZGF0ZShjYW5kc1tpXSk7XG4gICAgICAgIGlmIChjYW5kLnR5cGUgPT0gJ3NyZmx4Jykge1xuICAgICAgICAgICAgdGhpcy5oYWRzdHVuY2FuZGlkYXRlID0gdHJ1ZTtcbiAgICAgICAgfSBlbHNlIGlmIChjYW5kLnR5cGUgPT0gJ3JlbGF5Jykge1xuICAgICAgICAgICAgdGhpcy5oYWR0dXJuY2FuZGlkYXRlID0gdHJ1ZTtcbiAgICAgICAgfVxuICAgIH1cbn07XG5cbkppbmdsZVNlc3Npb24ucHJvdG90eXBlLnNldFJlbW90ZURlc2NyaXB0aW9uID0gZnVuY3Rpb24gKGVsZW0sIGRlc2N0eXBlKSB7XG4gICAgLy9jb25zb2xlLmxvZygnc2V0dGluZyByZW1vdGUgZGVzY3JpcHRpb24uLi4gJywgZGVzY3R5cGUpO1xuICAgIHRoaXMucmVtb3RlU0RQID0gbmV3IFNEUCgnJyk7XG4gICAgdGhpcy5yZW1vdGVTRFAuZnJvbUppbmdsZShlbGVtKTtcbiAgICBpZiAodGhpcy5wZWVyY29ubmVjdGlvbi5yZW1vdGVEZXNjcmlwdGlvbiAhPT0gbnVsbCkge1xuICAgICAgICBjb25zb2xlLmxvZygnc2V0UmVtb3RlRGVzY3JpcHRpb24gd2hlbiByZW1vdGUgZGVzY3JpcHRpb24gaXMgbm90IG51bGwsIHNob3VsZCBiZSBwcmFuc3dlcicsIHRoaXMucGVlcmNvbm5lY3Rpb24ucmVtb3RlRGVzY3JpcHRpb24pO1xuICAgICAgICBpZiAodGhpcy5wZWVyY29ubmVjdGlvbi5yZW1vdGVEZXNjcmlwdGlvbi50eXBlID09ICdwcmFuc3dlcicpIHtcbiAgICAgICAgICAgIHZhciBwcmFuc3dlciA9IG5ldyBTRFAodGhpcy5wZWVyY29ubmVjdGlvbi5yZW1vdGVEZXNjcmlwdGlvbi5zZHApO1xuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBwcmFuc3dlci5tZWRpYS5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIC8vIG1ha2Ugc3VyZSB3ZSBoYXZlIGljZSB1ZnJhZyBhbmQgcHdkXG4gICAgICAgICAgICAgICAgaWYgKCFTRFBVdGlsLmZpbmRfbGluZSh0aGlzLnJlbW90ZVNEUC5tZWRpYVtpXSwgJ2E9aWNlLXVmcmFnOicsIHRoaXMucmVtb3RlU0RQLnNlc3Npb24pKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChTRFBVdGlsLmZpbmRfbGluZShwcmFuc3dlci5tZWRpYVtpXSwgJ2E9aWNlLXVmcmFnOicsIHByYW5zd2VyLnNlc3Npb24pKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnJlbW90ZVNEUC5tZWRpYVtpXSArPSBTRFBVdGlsLmZpbmRfbGluZShwcmFuc3dlci5tZWRpYVtpXSwgJ2E9aWNlLXVmcmFnOicsIHByYW5zd2VyLnNlc3Npb24pICsgJ1xcclxcbic7XG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oJ25vIGljZSB1ZnJhZz8nKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBpZiAoU0RQVXRpbC5maW5kX2xpbmUocHJhbnN3ZXIubWVkaWFbaV0sICdhPWljZS1wd2Q6JywgcHJhbnN3ZXIuc2Vzc2lvbikpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMucmVtb3RlU0RQLm1lZGlhW2ldICs9IFNEUFV0aWwuZmluZF9saW5lKHByYW5zd2VyLm1lZGlhW2ldLCAnYT1pY2UtcHdkOicsIHByYW5zd2VyLnNlc3Npb24pICsgJ1xcclxcbic7XG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oJ25vIGljZSBwd2Q/Jyk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgLy8gY29weSBvdmVyIGNhbmRpZGF0ZXNcbiAgICAgICAgICAgICAgICB2YXIgbGluZXMgPSBTRFBVdGlsLmZpbmRfbGluZXMocHJhbnN3ZXIubWVkaWFbaV0sICdhPWNhbmRpZGF0ZTonKTtcbiAgICAgICAgICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGxpbmVzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMucmVtb3RlU0RQLm1lZGlhW2ldICs9IGxpbmVzW2pdICsgJ1xcclxcbic7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5yZW1vdGVTRFAucmF3ID0gdGhpcy5yZW1vdGVTRFAuc2Vzc2lvbiArIHRoaXMucmVtb3RlU0RQLm1lZGlhLmpvaW4oJycpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHZhciByZW1vdGVkZXNjID0gbmV3IFJUQ1Nlc3Npb25EZXNjcmlwdGlvbih7dHlwZTogZGVzY3R5cGUsIHNkcDogdGhpcy5yZW1vdGVTRFAucmF3fSk7XG5cbiAgICB0aGlzLnBlZXJjb25uZWN0aW9uLnNldFJlbW90ZURlc2NyaXB0aW9uKHJlbW90ZWRlc2MsXG4gICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIC8vY29uc29sZS5sb2coJ3NldFJlbW90ZURlc2NyaXB0aW9uIHN1Y2Nlc3MnKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24gKGUpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ3NldFJlbW90ZURlc2NyaXB0aW9uIGVycm9yJywgZSk7XG4gICAgICAgICAgICBKaW5nbGVTZXNzaW9uLm9uSmluZ2xlRmF0YWxFcnJvcihzZWxmLCBlKTtcbiAgICAgICAgfVxuICAgICk7XG59O1xuXG5KaW5nbGVTZXNzaW9uLnByb3RvdHlwZS5hZGRJY2VDYW5kaWRhdGUgPSBmdW5jdGlvbiAoZWxlbSkge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICBpZiAodGhpcy5wZWVyY29ubmVjdGlvbi5zaWduYWxpbmdTdGF0ZSA9PSAnY2xvc2VkJykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICghdGhpcy5wZWVyY29ubmVjdGlvbi5yZW1vdGVEZXNjcmlwdGlvbiAmJiB0aGlzLnBlZXJjb25uZWN0aW9uLnNpZ25hbGluZ1N0YXRlID09ICdoYXZlLWxvY2FsLW9mZmVyJykge1xuICAgICAgICBjb25zb2xlLmxvZygndHJpY2tsZSBpY2UgY2FuZGlkYXRlIGFycml2aW5nIGJlZm9yZSBzZXNzaW9uIGFjY2VwdC4uLicpO1xuICAgICAgICAvLyBjcmVhdGUgYSBQUkFOU1dFUiBmb3Igc2V0UmVtb3RlRGVzY3JpcHRpb25cbiAgICAgICAgaWYgKCF0aGlzLnJlbW90ZVNEUCkge1xuICAgICAgICAgICAgdmFyIGNvYmJsZWQgPSAndj0wXFxyXFxuJyArXG4gICAgICAgICAgICAgICAgJ289LSAnICsgJzE5MjM1MTg1MTYnICsgJyAyIElOIElQNCAwLjAuMC4wXFxyXFxuJyArLy8gRklYTUVcbiAgICAgICAgICAgICAgICAncz0tXFxyXFxuJyArXG4gICAgICAgICAgICAgICAgJ3Q9MCAwXFxyXFxuJztcbiAgICAgICAgICAgIC8vIGZpcnN0LCB0YWtlIHNvbWUgdGhpbmdzIGZyb20gdGhlIGxvY2FsIGRlc2NyaXB0aW9uXG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMubG9jYWxTRFAubWVkaWEubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICBjb2JibGVkICs9IFNEUFV0aWwuZmluZF9saW5lKHRoaXMubG9jYWxTRFAubWVkaWFbaV0sICdtPScpICsgJ1xcclxcbic7XG4gICAgICAgICAgICAgICAgY29iYmxlZCArPSBTRFBVdGlsLmZpbmRfbGluZXModGhpcy5sb2NhbFNEUC5tZWRpYVtpXSwgJ2E9cnRwbWFwOicpLmpvaW4oJ1xcclxcbicpICsgJ1xcclxcbic7XG4gICAgICAgICAgICAgICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKHRoaXMubG9jYWxTRFAubWVkaWFbaV0sICdhPW1pZDonKSkge1xuICAgICAgICAgICAgICAgICAgICBjb2JibGVkICs9IFNEUFV0aWwuZmluZF9saW5lKHRoaXMubG9jYWxTRFAubWVkaWFbaV0sICdhPW1pZDonKSArICdcXHJcXG4nO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBjb2JibGVkICs9ICdhPWluYWN0aXZlXFxyXFxuJztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRoaXMucmVtb3RlU0RQID0gbmV3IFNEUChjb2JibGVkKTtcbiAgICAgICAgfVxuICAgICAgICAvLyB0aGVuIGFkZCB0aGluZ3MgbGlrZSBpY2UgYW5kIGR0bHMgZnJvbSByZW1vdGUgY2FuZGlkYXRlXG4gICAgICAgIGVsZW0uZWFjaChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHNlbGYucmVtb3RlU0RQLm1lZGlhLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKHNlbGYucmVtb3RlU0RQLm1lZGlhW2ldLCAnYT1taWQ6JyArICQodGhpcykuYXR0cignbmFtZScpKSB8fFxuICAgICAgICAgICAgICAgICAgICBzZWxmLnJlbW90ZVNEUC5tZWRpYVtpXS5pbmRleE9mKCdtPScgKyAkKHRoaXMpLmF0dHIoJ25hbWUnKSkgPT09IDApIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKCFTRFBVdGlsLmZpbmRfbGluZShzZWxmLnJlbW90ZVNEUC5tZWRpYVtpXSwgJ2E9aWNlLXVmcmFnOicpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgdG1wID0gJCh0aGlzKS5maW5kKCd0cmFuc3BvcnQnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYucmVtb3RlU0RQLm1lZGlhW2ldICs9ICdhPWljZS11ZnJhZzonICsgdG1wLmF0dHIoJ3VmcmFnJykgKyAnXFxyXFxuJztcbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYucmVtb3RlU0RQLm1lZGlhW2ldICs9ICdhPWljZS1wd2Q6JyArIHRtcC5hdHRyKCdwd2QnKSArICdcXHJcXG4nO1xuICAgICAgICAgICAgICAgICAgICAgICAgdG1wID0gJCh0aGlzKS5maW5kKCd0cmFuc3BvcnQ+ZmluZ2VycHJpbnQnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmICh0bXAubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZi5yZW1vdGVTRFAubWVkaWFbaV0gKz0gJ2E9ZmluZ2VycHJpbnQ6JyArIHRtcC5hdHRyKCdoYXNoJykgKyAnICcgKyB0bXAudGV4dCgpICsgJ1xcclxcbic7XG4gICAgICAgICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCdubyBkdGxzIGZpbmdlcnByaW50ICh3ZWJydGMgaXNzdWUgIzE3MTg/KScpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYucmVtb3RlU0RQLm1lZGlhW2ldICs9ICdhPWNyeXB0bzoxIEFFU19DTV8xMjhfSE1BQ19TSEExXzgwIGlubGluZTpCQUFEQkFBREJBQURCQUFEQkFBREJBQURCQUFEQkFBREJBQURCQUFEXFxyXFxuJztcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5yZW1vdGVTRFAucmF3ID0gdGhpcy5yZW1vdGVTRFAuc2Vzc2lvbiArIHRoaXMucmVtb3RlU0RQLm1lZGlhLmpvaW4oJycpO1xuXG4gICAgICAgIC8vIHdlIG5lZWQgYSBjb21wbGV0ZSBTRFAgd2l0aCBpY2UtdWZyYWcvaWNlLXB3ZCBpbiBhbGwgcGFydHNcbiAgICAgICAgLy8gdGhpcyBtYWtlcyB0aGUgYXNzdW1wdGlvbiB0aGF0IHRoZSBQUkFOU1dFUiBpcyBjb25zdHJ1Y3RlZCBzdWNoIHRoYXQgdGhlIGljZS11ZnJhZyBpcyBpbiBhbGwgbWVkaWFwYXJ0c1xuICAgICAgICAvLyBidXQgaXQgY291bGQgYmUgaW4gdGhlIHNlc3Npb24gcGFydCBhcyB3ZWxsLiBzaW5jZSB0aGUgY29kZSBhYm92ZSBjb25zdHJ1Y3RzIHRoaXMgc2RwIHRoaXMgY2FuJ3QgaGFwcGVuIGhvd2V2ZXJcbiAgICAgICAgdmFyIGlzY29tcGxldGUgPSB0aGlzLnJlbW90ZVNEUC5tZWRpYS5maWx0ZXIoZnVuY3Rpb24gKG1lZGlhcGFydCkge1xuICAgICAgICAgICAgcmV0dXJuIFNEUFV0aWwuZmluZF9saW5lKG1lZGlhcGFydCwgJ2E9aWNlLXVmcmFnOicpO1xuICAgICAgICB9KS5sZW5ndGggPT0gdGhpcy5yZW1vdGVTRFAubWVkaWEubGVuZ3RoO1xuXG4gICAgICAgIGlmIChpc2NvbXBsZXRlKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZygnc2V0dGluZyBwcmFuc3dlcicpO1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICB0aGlzLnBlZXJjb25uZWN0aW9uLnNldFJlbW90ZURlc2NyaXB0aW9uKG5ldyBSVENTZXNzaW9uRGVzY3JpcHRpb24oe3R5cGU6ICdwcmFuc3dlcicsIHNkcDogdGhpcy5yZW1vdGVTRFAucmF3IH0pLFxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgY29uc29sZS5sb2coJ3NldFJlbW90ZURlc2NyaXB0aW9uIHByYW5zd2VyIGZhaWxlZCcsIGUudG9TdHJpbmcoKSk7XG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ3NldHRpbmcgcHJhbnN3ZXIgZmFpbGVkJywgZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvL2NvbnNvbGUubG9nKCdub3QgeWV0IHNldHRpbmcgcHJhbnN3ZXInKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvLyBvcGVyYXRlIG9uIGVhY2ggY29udGVudCBlbGVtZW50XG4gICAgZWxlbS5lYWNoKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgLy8gd291bGQgbG92ZSB0byBkZWFjdGl2YXRlIHRoaXMsIGJ1dCBmaXJlZm94IHN0aWxsIHJlcXVpcmVzIGl0XG4gICAgICAgIHZhciBpZHggPSAtMTtcbiAgICAgICAgdmFyIGk7XG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBzZWxmLnJlbW90ZVNEUC5tZWRpYS5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKHNlbGYucmVtb3RlU0RQLm1lZGlhW2ldLCAnYT1taWQ6JyArICQodGhpcykuYXR0cignbmFtZScpKSB8fFxuICAgICAgICAgICAgICAgIHNlbGYucmVtb3RlU0RQLm1lZGlhW2ldLmluZGV4T2YoJ209JyArICQodGhpcykuYXR0cignbmFtZScpKSA9PT0gMCkge1xuICAgICAgICAgICAgICAgIGlkeCA9IGk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGlkeCA9PSAtMSkgeyAvLyBmYWxsIGJhY2sgdG8gbG9jYWxkZXNjcmlwdGlvblxuICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IHNlbGYubG9jYWxTRFAubWVkaWEubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICBpZiAoU0RQVXRpbC5maW5kX2xpbmUoc2VsZi5sb2NhbFNEUC5tZWRpYVtpXSwgJ2E9bWlkOicgKyAkKHRoaXMpLmF0dHIoJ25hbWUnKSkgfHxcbiAgICAgICAgICAgICAgICAgICAgc2VsZi5sb2NhbFNEUC5tZWRpYVtpXS5pbmRleE9mKCdtPScgKyAkKHRoaXMpLmF0dHIoJ25hbWUnKSkgPT09IDApIHtcbiAgICAgICAgICAgICAgICAgICAgaWR4ID0gaTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHZhciBuYW1lID0gJCh0aGlzKS5hdHRyKCduYW1lJyk7XG4gICAgICAgIC8vIFRPRE86IGNoZWNrIGljZS1wd2QgYW5kIGljZS11ZnJhZz9cbiAgICAgICAgJCh0aGlzKS5maW5kKCd0cmFuc3BvcnQ+Y2FuZGlkYXRlJykuZWFjaChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICB2YXIgbGluZSwgY2FuZGlkYXRlO1xuICAgICAgICAgICAgbGluZSA9IFNEUFV0aWwuY2FuZGlkYXRlRnJvbUppbmdsZSh0aGlzKTtcbiAgICAgICAgICAgIGNhbmRpZGF0ZSA9IG5ldyBSVENJY2VDYW5kaWRhdGUoe3NkcE1MaW5lSW5kZXg6IGlkeCxcbiAgICAgICAgICAgICAgICBzZHBNaWQ6IG5hbWUsXG4gICAgICAgICAgICAgICAgY2FuZGlkYXRlOiBsaW5lfSk7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIHNlbGYucGVlcmNvbm5lY3Rpb24uYWRkSWNlQ2FuZGlkYXRlKGNhbmRpZGF0ZSk7XG4gICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS5lcnJvcignYWRkSWNlQ2FuZGlkYXRlIGZhaWxlZCcsIGUudG9TdHJpbmcoKSwgbGluZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH0pO1xufTtcblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuc2VuZEFuc3dlciA9IGZ1bmN0aW9uIChwcm92aXNpb25hbCkge1xuICAgIC8vY29uc29sZS5sb2coJ2NyZWF0ZUFuc3dlcicsIHByb3Zpc2lvbmFsKTtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgdGhpcy5wZWVyY29ubmVjdGlvbi5jcmVhdGVBbnN3ZXIoXG4gICAgICAgIGZ1bmN0aW9uIChzZHApIHtcbiAgICAgICAgICAgIHNlbGYuY3JlYXRlZEFuc3dlcihzZHAsIHByb3Zpc2lvbmFsKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24gKGUpIHtcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ2NyZWF0ZUFuc3dlciBmYWlsZWQnLCBlKTtcbiAgICAgICAgfSxcbiAgICAgICAgdGhpcy5tZWRpYV9jb25zdHJhaW50c1xuICAgICk7XG59O1xuXG5KaW5nbGVTZXNzaW9uLnByb3RvdHlwZS5jcmVhdGVkQW5zd2VyID0gZnVuY3Rpb24gKHNkcCwgcHJvdmlzaW9uYWwpIHtcbiAgICAvL2NvbnNvbGUubG9nKCdjcmVhdGVBbnN3ZXIgY2FsbGJhY2snKTtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgdGhpcy5sb2NhbFNEUCA9IG5ldyBTRFAoc2RwLnNkcCk7XG4gICAgLy90aGlzLmxvY2FsU0RQLm1hbmdsZSgpO1xuICAgIHRoaXMudXNlcHJhbnN3ZXIgPSBwcm92aXNpb25hbCA9PT0gdHJ1ZTtcbiAgICBpZiAodGhpcy51c2V0cmlja2xlKSB7XG4gICAgICAgIGlmICh0aGlzLnVzZXByYW5zd2VyKSB7XG4gICAgICAgICAgICBzZHAudHlwZSA9ICdwcmFuc3dlcic7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMubG9jYWxTRFAubWVkaWEubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLmxvY2FsU0RQLm1lZGlhW2ldID0gdGhpcy5sb2NhbFNEUC5tZWRpYVtpXS5yZXBsYWNlKCdhPXNlbmRyZWN2XFxyXFxuJywgJ2E9aW5hY3RpdmVcXHJcXG4nKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRoaXMubG9jYWxTRFAucmF3ID0gdGhpcy5sb2NhbFNEUC5zZXNzaW9uICsgJ1xcclxcbicgKyB0aGlzLmxvY2FsU0RQLm1lZGlhLmpvaW4oJycpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB2YXIgc2VuZEppbmdsZSA9IGZ1bmN0aW9uIChzc3Jjcykge1xuXG4gICAgICAgICAgICAgICAgdmFyIGFjY2VwdCA9ICRpcSh7dG86IHNlbGYucGVlcmppZCxcbiAgICAgICAgICAgICAgICAgICAgdHlwZTogJ3NldCd9KVxuICAgICAgICAgICAgICAgICAgICAuYygnamluZ2xlJywge3htbG5zOiAndXJuOnhtcHA6amluZ2xlOjEnLFxuICAgICAgICAgICAgICAgICAgICAgICAgYWN0aW9uOiAnc2Vzc2lvbi1hY2NlcHQnLFxuICAgICAgICAgICAgICAgICAgICAgICAgaW5pdGlhdG9yOiBzZWxmLmluaXRpYXRvcixcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3BvbmRlcjogc2VsZi5yZXNwb25kZXIsXG4gICAgICAgICAgICAgICAgICAgICAgICBzaWQ6IHNlbGYuc2lkIH0pO1xuICAgICAgICAgICAgICAgIHZhciBwdWJsaWNMb2NhbERlc2MgPSBzaW11bGNhc3QucmV2ZXJzZVRyYW5zZm9ybUxvY2FsRGVzY3JpcHRpb24oc2RwKTtcbiAgICAgICAgICAgICAgICB2YXIgcHVibGljTG9jYWxTRFAgPSBuZXcgU0RQKHB1YmxpY0xvY2FsRGVzYy5zZHApO1xuICAgICAgICAgICAgICAgIHB1YmxpY0xvY2FsU0RQLnRvSmluZ2xlKGFjY2VwdCwgc2VsZi5pbml0aWF0b3IgPT0gc2VsZi5tZSA/ICdpbml0aWF0b3InIDogJ3Jlc3BvbmRlcicsIHNzcmNzKTtcbiAgICAgICAgICAgICAgICBzZWxmLmNvbm5lY3Rpb24uc2VuZElRKGFjY2VwdCxcbiAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGFjayA9IHt9O1xuICAgICAgICAgICAgICAgICAgICAgICAgYWNrLnNvdXJjZSA9ICdhbnN3ZXInO1xuICAgICAgICAgICAgICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcignYWNrLmppbmdsZScsIFtzZWxmLnNpZCwgYWNrXSk7XG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChzdGFuemEpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlcnJvciA9ICgkKHN0YW56YSkuZmluZCgnZXJyb3InKS5sZW5ndGgpID8ge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvZGU6ICQoc3RhbnphKS5maW5kKCdlcnJvcicpLmF0dHIoJ2NvZGUnKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWFzb246ICQoc3RhbnphKS5maW5kKCdlcnJvciA6Zmlyc3QnKVswXS50YWdOYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgfTp7fTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGVycm9yLnNvdXJjZSA9ICdhbnN3ZXInO1xuICAgICAgICAgICAgICAgICAgICAgICAgSmluZ2xlU2Vzc2lvbi5vbkppbmdsZUVycm9yKHNlbGYuc2lkLCBlcnJvcik7XG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgIDEwMDAwKTtcbiAgICB9XG4gICAgc2RwLnNkcCA9IHRoaXMubG9jYWxTRFAucmF3O1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24uc2V0TG9jYWxEZXNjcmlwdGlvbihzZHAsXG4gICAgICAgIGZ1bmN0aW9uICgpIHtcblxuICAgICAgICAgICAgLy9jb25zb2xlLmxvZygnc2V0TG9jYWxEZXNjcmlwdGlvbiBzdWNjZXNzJyk7XG4gICAgICAgICAgICBpZiAoc2VsZi51c2V0cmlja2xlICYmICFzZWxmLnVzZXByYW5zd2VyKSB7XG4gICAgICAgICAgICAgICAgc2VuZEppbmdsZSgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgc2VsZi5zZXRMb2NhbERlc2NyaXB0aW9uKCk7XG4gICAgICAgIH0sXG4gICAgICAgIGZ1bmN0aW9uIChlKSB7XG4gICAgICAgICAgICBjb25zb2xlLmVycm9yKCdzZXRMb2NhbERlc2NyaXB0aW9uIGZhaWxlZCcsIGUpO1xuICAgICAgICB9XG4gICAgKTtcbiAgICB2YXIgY2FuZHMgPSBTRFBVdGlsLmZpbmRfbGluZXModGhpcy5sb2NhbFNEUC5yYXcsICdhPWNhbmRpZGF0ZTonKTtcbiAgICBmb3IgKHZhciBqID0gMDsgaiA8IGNhbmRzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgIHZhciBjYW5kID0gU0RQVXRpbC5wYXJzZV9pY2VjYW5kaWRhdGUoY2FuZHNbal0pO1xuICAgICAgICBpZiAoY2FuZC50eXBlID09ICdzcmZseCcpIHtcbiAgICAgICAgICAgIHRoaXMuaGFkc3R1bmNhbmRpZGF0ZSA9IHRydWU7XG4gICAgICAgIH0gZWxzZSBpZiAoY2FuZC50eXBlID09ICdyZWxheScpIHtcbiAgICAgICAgICAgIHRoaXMuaGFkdHVybmNhbmRpZGF0ZSA9IHRydWU7XG4gICAgICAgIH1cbiAgICB9XG59O1xuXG5KaW5nbGVTZXNzaW9uLnByb3RvdHlwZS5zZW5kVGVybWluYXRlID0gZnVuY3Rpb24gKHJlYXNvbiwgdGV4dCkge1xuICAgIHZhciBzZWxmID0gdGhpcyxcbiAgICAgICAgdGVybSA9ICRpcSh7dG86IHRoaXMucGVlcmppZCxcbiAgICAgICAgICAgIHR5cGU6ICdzZXQnfSlcbiAgICAgICAgICAgIC5jKCdqaW5nbGUnLCB7eG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6MScsXG4gICAgICAgICAgICAgICAgYWN0aW9uOiAnc2Vzc2lvbi10ZXJtaW5hdGUnLFxuICAgICAgICAgICAgICAgIGluaXRpYXRvcjogdGhpcy5pbml0aWF0b3IsXG4gICAgICAgICAgICAgICAgc2lkOiB0aGlzLnNpZH0pXG4gICAgICAgICAgICAuYygncmVhc29uJylcbiAgICAgICAgICAgIC5jKHJlYXNvbiB8fCAnc3VjY2VzcycpO1xuXG4gICAgaWYgKHRleHQpIHtcbiAgICAgICAgdGVybS51cCgpLmMoJ3RleHQnKS50KHRleHQpO1xuICAgIH1cblxuICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kSVEodGVybSxcbiAgICAgICAgZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgc2VsZi5wZWVyY29ubmVjdGlvbi5jbG9zZSgpO1xuICAgICAgICAgICAgc2VsZi5wZWVyY29ubmVjdGlvbiA9IG51bGw7XG4gICAgICAgICAgICBzZWxmLnRlcm1pbmF0ZSgpO1xuICAgICAgICAgICAgdmFyIGFjayA9IHt9O1xuICAgICAgICAgICAgYWNrLnNvdXJjZSA9ICd0ZXJtaW5hdGUnO1xuICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcignYWNrLmppbmdsZScsIFtzZWxmLnNpZCwgYWNrXSk7XG4gICAgICAgIH0sXG4gICAgICAgIGZ1bmN0aW9uIChzdGFuemEpIHtcbiAgICAgICAgICAgIHZhciBlcnJvciA9ICgkKHN0YW56YSkuZmluZCgnZXJyb3InKS5sZW5ndGgpID8ge1xuICAgICAgICAgICAgICAgIGNvZGU6ICQoc3RhbnphKS5maW5kKCdlcnJvcicpLmF0dHIoJ2NvZGUnKSxcbiAgICAgICAgICAgICAgICByZWFzb246ICQoc3RhbnphKS5maW5kKCdlcnJvciA6Zmlyc3QnKVswXS50YWdOYW1lLFxuICAgICAgICAgICAgfTp7fTtcbiAgICAgICAgICAgICQoZG9jdW1lbnQpLnRyaWdnZXIoJ2Fjay5qaW5nbGUnLCBbc2VsZi5zaWQsIGVycm9yXSk7XG4gICAgICAgIH0sXG4gICAgICAgIDEwMDAwKTtcbiAgICBpZiAodGhpcy5zdGF0c2ludGVydmFsICE9PSBudWxsKSB7XG4gICAgICAgIHdpbmRvdy5jbGVhckludGVydmFsKHRoaXMuc3RhdHNpbnRlcnZhbCk7XG4gICAgICAgIHRoaXMuc3RhdHNpbnRlcnZhbCA9IG51bGw7XG4gICAgfVxufTtcblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuYWRkU291cmNlID0gZnVuY3Rpb24gKGVsZW0sIGZyb21KaWQpIHtcblxuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAvLyBGSVhNRTogZGlydHkgd2FpdGluZ1xuICAgIGlmICghdGhpcy5wZWVyY29ubmVjdGlvbi5sb2NhbERlc2NyaXB0aW9uKVxuICAgIHtcbiAgICAgICAgY29uc29sZS53YXJuKFwiYWRkU291cmNlIC0gbG9jYWxEZXNjcmlwdGlvbiBub3QgcmVhZHkgeWV0XCIpXG4gICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24oKVxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHNlbGYuYWRkU291cmNlKGVsZW0sIGZyb21KaWQpO1xuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIDIwMFxuICAgICAgICApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc29sZS5sb2coJ2FkZHNzcmMnLCBuZXcgRGF0ZSgpLmdldFRpbWUoKSk7XG4gICAgY29uc29sZS5sb2coJ2ljZScsIHRoaXMucGVlcmNvbm5lY3Rpb24uaWNlQ29ubmVjdGlvblN0YXRlKTtcbiAgICB2YXIgc2RwID0gbmV3IFNEUCh0aGlzLnBlZXJjb25uZWN0aW9uLnJlbW90ZURlc2NyaXB0aW9uLnNkcCk7XG4gICAgdmFyIG15U2RwID0gbmV3IFNEUCh0aGlzLnBlZXJjb25uZWN0aW9uLmxvY2FsRGVzY3JpcHRpb24uc2RwKTtcblxuICAgICQoZWxlbSkuZWFjaChmdW5jdGlvbiAoaWR4LCBjb250ZW50KSB7XG4gICAgICAgIHZhciBuYW1lID0gJChjb250ZW50KS5hdHRyKCduYW1lJyk7XG4gICAgICAgIHZhciBsaW5lcyA9ICcnO1xuICAgICAgICB0bXAgPSAkKGNvbnRlbnQpLmZpbmQoJ3NzcmMtZ3JvdXBbeG1sbnM9XCJ1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6c3NtYTowXCJdJykuZWFjaChmdW5jdGlvbigpIHtcbiAgICAgICAgICAgIHZhciBzZW1hbnRpY3MgPSB0aGlzLmdldEF0dHJpYnV0ZSgnc2VtYW50aWNzJyk7XG4gICAgICAgICAgICB2YXIgc3NyY3MgPSAkKHRoaXMpLmZpbmQoJz5zb3VyY2UnKS5tYXAoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLmdldEF0dHJpYnV0ZSgnc3NyYycpO1xuICAgICAgICAgICAgfSkuZ2V0KCk7XG5cbiAgICAgICAgICAgIGlmIChzc3Jjcy5sZW5ndGggIT0gMCkge1xuICAgICAgICAgICAgICAgIGxpbmVzICs9ICdhPXNzcmMtZ3JvdXA6JyArIHNlbWFudGljcyArICcgJyArIHNzcmNzLmpvaW4oJyAnKSArICdcXHJcXG4nO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgdG1wID0gJChjb250ZW50KS5maW5kKCdzb3VyY2VbeG1sbnM9XCJ1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6c3NtYTowXCJdJyk7IC8vIGNhbiBoYW5kbGUgYm90aCA+c291cmNlIGFuZCA+ZGVzY3JpcHRpb24+c291cmNlXG4gICAgICAgIHRtcC5lYWNoKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHZhciBzc3JjID0gJCh0aGlzKS5hdHRyKCdzc3JjJyk7XG4gICAgICAgICAgICBpZihteVNkcC5jb250YWluc1NTUkMoc3NyYykpe1xuICAgICAgICAgICAgICAgIC8qKlxuICAgICAgICAgICAgICAgICAqIFRoaXMgaGFwcGVucyB3aGVuIG11bHRpcGxlIHBhcnRpY2lwYW50cyBjaGFuZ2UgdGhlaXIgc3RyZWFtcyBhdCB0aGUgc2FtZSB0aW1lIGFuZFxuICAgICAgICAgICAgICAgICAqIENvbGlicmlGb2N1cy5tb2RpZnlTb3VyY2VzIGhhdmUgdG8gd2FpdCBmb3Igc3RhYmxlIHN0YXRlLiBJbiB0aGUgbWVhbnRpbWUgbXVsdGlwbGVcbiAgICAgICAgICAgICAgICAgKiBhZGRzc3JjIGFyZSBzY2hlZHVsZWQgZm9yIHVwZGF0ZSBJUS4gU2VlXG4gICAgICAgICAgICAgICAgICovXG4gICAgICAgICAgICAgICAgY29uc29sZS53YXJuKFwiR290IGFkZCBzdHJlYW0gcmVxdWVzdCBmb3IgbXkgb3duIHNzcmM6IFwiK3NzcmMpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgICQodGhpcykuZmluZCgnPnBhcmFtZXRlcicpLmVhY2goZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIGxpbmVzICs9ICdhPXNzcmM6JyArIHNzcmMgKyAnICcgKyAkKHRoaXMpLmF0dHIoJ25hbWUnKTtcbiAgICAgICAgICAgICAgICBpZiAoJCh0aGlzKS5hdHRyKCd2YWx1ZScpICYmICQodGhpcykuYXR0cigndmFsdWUnKS5sZW5ndGgpXG4gICAgICAgICAgICAgICAgICAgIGxpbmVzICs9ICc6JyArICQodGhpcykuYXR0cigndmFsdWUnKTtcbiAgICAgICAgICAgICAgICBsaW5lcyArPSAnXFxyXFxuJztcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcbiAgICAgICAgc2RwLm1lZGlhLmZvckVhY2goZnVuY3Rpb24obWVkaWEsIGlkeCkge1xuICAgICAgICAgICAgaWYgKCFTRFBVdGlsLmZpbmRfbGluZShtZWRpYSwgJ2E9bWlkOicgKyBuYW1lKSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICBzZHAubWVkaWFbaWR4XSArPSBsaW5lcztcbiAgICAgICAgICAgIGlmICghc2VsZi5hZGRzc3JjW2lkeF0pIHNlbGYuYWRkc3NyY1tpZHhdID0gJyc7XG4gICAgICAgICAgICBzZWxmLmFkZHNzcmNbaWR4XSArPSBsaW5lcztcbiAgICAgICAgfSk7XG4gICAgICAgIHNkcC5yYXcgPSBzZHAuc2Vzc2lvbiArIHNkcC5tZWRpYS5qb2luKCcnKTtcbiAgICB9KTtcbiAgICB0aGlzLm1vZGlmeVNvdXJjZXMoKTtcbn07XG5cbkppbmdsZVNlc3Npb24ucHJvdG90eXBlLnJlbW92ZVNvdXJjZSA9IGZ1bmN0aW9uIChlbGVtLCBmcm9tSmlkKSB7XG5cbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgLy8gRklYTUU6IGRpcnR5IHdhaXRpbmdcbiAgICBpZiAoIXRoaXMucGVlcmNvbm5lY3Rpb24ubG9jYWxEZXNjcmlwdGlvbilcbiAgICB7XG4gICAgICAgIGNvbnNvbGUud2FybihcInJlbW92ZVNvdXJjZSAtIGxvY2FsRGVzY3JpcHRpb24gbm90IHJlYWR5IHlldFwiKVxuICAgICAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uKClcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICBzZWxmLnJlbW92ZVNvdXJjZShlbGVtLCBmcm9tSmlkKTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAyMDBcbiAgICAgICAgKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnNvbGUubG9nKCdyZW1vdmVzc3JjJywgbmV3IERhdGUoKS5nZXRUaW1lKCkpO1xuICAgIGNvbnNvbGUubG9nKCdpY2UnLCB0aGlzLnBlZXJjb25uZWN0aW9uLmljZUNvbm5lY3Rpb25TdGF0ZSk7XG4gICAgdmFyIHNkcCA9IG5ldyBTRFAodGhpcy5wZWVyY29ubmVjdGlvbi5yZW1vdGVEZXNjcmlwdGlvbi5zZHApO1xuICAgIHZhciBteVNkcCA9IG5ldyBTRFAodGhpcy5wZWVyY29ubmVjdGlvbi5sb2NhbERlc2NyaXB0aW9uLnNkcCk7XG5cbiAgICAkKGVsZW0pLmVhY2goZnVuY3Rpb24gKGlkeCwgY29udGVudCkge1xuICAgICAgICB2YXIgbmFtZSA9ICQoY29udGVudCkuYXR0cignbmFtZScpO1xuICAgICAgICB2YXIgbGluZXMgPSAnJztcbiAgICAgICAgdG1wID0gJChjb250ZW50KS5maW5kKCdzc3JjLWdyb3VwW3htbG5zPVwidXJuOnhtcHA6amluZ2xlOmFwcHM6cnRwOnNzbWE6MFwiXScpLmVhY2goZnVuY3Rpb24oKSB7XG4gICAgICAgICAgICB2YXIgc2VtYW50aWNzID0gdGhpcy5nZXRBdHRyaWJ1dGUoJ3NlbWFudGljcycpO1xuICAgICAgICAgICAgdmFyIHNzcmNzID0gJCh0aGlzKS5maW5kKCc+c291cmNlJykubWFwKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5nZXRBdHRyaWJ1dGUoJ3NzcmMnKTtcbiAgICAgICAgICAgIH0pLmdldCgpO1xuXG4gICAgICAgICAgICBpZiAoc3NyY3MubGVuZ3RoICE9IDApIHtcbiAgICAgICAgICAgICAgICBsaW5lcyArPSAnYT1zc3JjLWdyb3VwOicgKyBzZW1hbnRpY3MgKyAnICcgKyBzc3Jjcy5qb2luKCcgJykgKyAnXFxyXFxuJztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIHRtcCA9ICQoY29udGVudCkuZmluZCgnc291cmNlW3htbG5zPVwidXJuOnhtcHA6amluZ2xlOmFwcHM6cnRwOnNzbWE6MFwiXScpOyAvLyBjYW4gaGFuZGxlIGJvdGggPnNvdXJjZSBhbmQgPmRlc2NyaXB0aW9uPnNvdXJjZVxuICAgICAgICB0bXAuZWFjaChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICB2YXIgc3NyYyA9ICQodGhpcykuYXR0cignc3NyYycpO1xuICAgICAgICAgICAgLy8gVGhpcyBzaG91bGQgbmV2ZXIgaGFwcGVuLCBidXQgY2FuIGJlIHVzZWZ1bCBmb3IgYnVnIGRldGVjdGlvblxuICAgICAgICAgICAgaWYobXlTZHAuY29udGFpbnNTU1JDKHNzcmMpKXtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKFwiR290IHJlbW92ZSBzdHJlYW0gcmVxdWVzdCBmb3IgbXkgb3duIHNzcmM6IFwiK3NzcmMpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgICQodGhpcykuZmluZCgnPnBhcmFtZXRlcicpLmVhY2goZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgICAgIGxpbmVzICs9ICdhPXNzcmM6JyArIHNzcmMgKyAnICcgKyAkKHRoaXMpLmF0dHIoJ25hbWUnKTtcbiAgICAgICAgICAgICAgICBpZiAoJCh0aGlzKS5hdHRyKCd2YWx1ZScpICYmICQodGhpcykuYXR0cigndmFsdWUnKS5sZW5ndGgpXG4gICAgICAgICAgICAgICAgICAgIGxpbmVzICs9ICc6JyArICQodGhpcykuYXR0cigndmFsdWUnKTtcbiAgICAgICAgICAgICAgICBsaW5lcyArPSAnXFxyXFxuJztcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcbiAgICAgICAgc2RwLm1lZGlhLmZvckVhY2goZnVuY3Rpb24obWVkaWEsIGlkeCkge1xuICAgICAgICAgICAgaWYgKCFTRFBVdGlsLmZpbmRfbGluZShtZWRpYSwgJ2E9bWlkOicgKyBuYW1lKSlcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICBzZHAubWVkaWFbaWR4XSArPSBsaW5lcztcbiAgICAgICAgICAgIGlmICghc2VsZi5yZW1vdmVzc3JjW2lkeF0pIHNlbGYucmVtb3Zlc3NyY1tpZHhdID0gJyc7XG4gICAgICAgICAgICBzZWxmLnJlbW92ZXNzcmNbaWR4XSArPSBsaW5lcztcbiAgICAgICAgfSk7XG4gICAgICAgIHNkcC5yYXcgPSBzZHAuc2Vzc2lvbiArIHNkcC5tZWRpYS5qb2luKCcnKTtcbiAgICB9KTtcbiAgICB0aGlzLm1vZGlmeVNvdXJjZXMoKTtcbn07XG5cbkppbmdsZVNlc3Npb24ucHJvdG90eXBlLm1vZGlmeVNvdXJjZXMgPSBmdW5jdGlvbiAoc3VjY2Vzc0NhbGxiYWNrKSB7XG4gICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgIGlmICh0aGlzLnBlZXJjb25uZWN0aW9uLnNpZ25hbGluZ1N0YXRlID09ICdjbG9zZWQnKSByZXR1cm47XG4gICAgaWYgKCEodGhpcy5hZGRzc3JjLmxlbmd0aCB8fCB0aGlzLnJlbW92ZXNzcmMubGVuZ3RoIHx8IHRoaXMucGVuZGluZ29wICE9PSBudWxsIHx8IHRoaXMuc3dpdGNoc3RyZWFtcykpe1xuICAgICAgICAvLyBUaGVyZSBpcyBub3RoaW5nIHRvIGRvIHNpbmNlIHNjaGVkdWxlZCBqb2IgbWlnaHQgaGF2ZSBiZWVuIGV4ZWN1dGVkIGJ5IGFub3RoZXIgc3VjY2VlZGluZyBjYWxsXG4gICAgICAgIHRoaXMuc2V0TG9jYWxEZXNjcmlwdGlvbigpO1xuICAgICAgICBpZihzdWNjZXNzQ2FsbGJhY2spe1xuICAgICAgICAgICAgc3VjY2Vzc0NhbGxiYWNrKCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIEZJWE1FOiB0aGlzIGlzIGEgYmlnIGhhY2tcbiAgICAvLyBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL3dlYnJ0Yy9pc3N1ZXMvZGV0YWlsP2lkPTI2ODhcbiAgICAvLyBeIGhhcyBiZWVuIGZpeGVkLlxuICAgIGlmICghKHRoaXMucGVlcmNvbm5lY3Rpb24uc2lnbmFsaW5nU3RhdGUgPT0gJ3N0YWJsZScgJiYgdGhpcy5wZWVyY29ubmVjdGlvbi5pY2VDb25uZWN0aW9uU3RhdGUgPT0gJ2Nvbm5lY3RlZCcpKSB7XG4gICAgICAgIGNvbnNvbGUud2FybignbW9kaWZ5U291cmNlcyBub3QgeWV0JywgdGhpcy5wZWVyY29ubmVjdGlvbi5zaWduYWxpbmdTdGF0ZSwgdGhpcy5wZWVyY29ubmVjdGlvbi5pY2VDb25uZWN0aW9uU3RhdGUpO1xuICAgICAgICB0aGlzLndhaXQgPSB0cnVlO1xuICAgICAgICB3aW5kb3cuc2V0VGltZW91dChmdW5jdGlvbigpIHsgc2VsZi5tb2RpZnlTb3VyY2VzKHN1Y2Nlc3NDYWxsYmFjayk7IH0sIDI1MCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKHRoaXMud2FpdCkge1xuICAgICAgICB3aW5kb3cuc2V0VGltZW91dChmdW5jdGlvbigpIHsgc2VsZi5tb2RpZnlTb3VyY2VzKHN1Y2Nlc3NDYWxsYmFjayk7IH0sIDI1MDApO1xuICAgICAgICB0aGlzLndhaXQgPSBmYWxzZTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIFJlc2V0IHN3aXRjaCBzdHJlYW1zIGZsYWdcbiAgICB0aGlzLnN3aXRjaHN0cmVhbXMgPSBmYWxzZTtcblxuICAgIHZhciBzZHAgPSBuZXcgU0RQKHRoaXMucGVlcmNvbm5lY3Rpb24ucmVtb3RlRGVzY3JpcHRpb24uc2RwKTtcblxuICAgIC8vIGFkZCBzb3VyY2VzXG4gICAgdGhpcy5hZGRzc3JjLmZvckVhY2goZnVuY3Rpb24obGluZXMsIGlkeCkge1xuICAgICAgICBzZHAubWVkaWFbaWR4XSArPSBsaW5lcztcbiAgICB9KTtcbiAgICB0aGlzLmFkZHNzcmMgPSBbXTtcblxuICAgIC8vIHJlbW92ZSBzb3VyY2VzXG4gICAgdGhpcy5yZW1vdmVzc3JjLmZvckVhY2goZnVuY3Rpb24obGluZXMsIGlkeCkge1xuICAgICAgICBsaW5lcyA9IGxpbmVzLnNwbGl0KCdcXHJcXG4nKTtcbiAgICAgICAgbGluZXMucG9wKCk7IC8vIHJlbW92ZSBlbXB0eSBsYXN0IGVsZW1lbnQ7XG4gICAgICAgIGxpbmVzLmZvckVhY2goZnVuY3Rpb24obGluZSkge1xuICAgICAgICAgICAgc2RwLm1lZGlhW2lkeF0gPSBzZHAubWVkaWFbaWR4XS5yZXBsYWNlKGxpbmUgKyAnXFxyXFxuJywgJycpO1xuICAgICAgICB9KTtcbiAgICB9KTtcbiAgICB0aGlzLnJlbW92ZXNzcmMgPSBbXTtcblxuICAgIC8vIEZJWE1FOlxuICAgIC8vIHRoaXMgd2FzIGEgaGFjayBmb3IgdGhlIHNpdHVhdGlvbiB3aGVuIG9ubHkgb25lIHBlZXIgZXhpc3RzXG4gICAgLy8gaW4gdGhlIGNvbmZlcmVuY2UuXG4gICAgLy8gY2hlY2sgaWYgc3RpbGwgcmVxdWlyZWQgYW5kIHJlbW92ZVxuICAgIGlmIChzZHAubWVkaWFbMF0pXG4gICAgICAgIHNkcC5tZWRpYVswXSA9IHNkcC5tZWRpYVswXS5yZXBsYWNlKCdhPXJlY3Zvbmx5JywgJ2E9c2VuZHJlY3YnKTtcbiAgICBpZiAoc2RwLm1lZGlhWzFdKVxuICAgICAgICBzZHAubWVkaWFbMV0gPSBzZHAubWVkaWFbMV0ucmVwbGFjZSgnYT1yZWN2b25seScsICdhPXNlbmRyZWN2Jyk7XG5cbiAgICBzZHAucmF3ID0gc2RwLnNlc3Npb24gKyBzZHAubWVkaWEuam9pbignJyk7XG4gICAgdGhpcy5wZWVyY29ubmVjdGlvbi5zZXRSZW1vdGVEZXNjcmlwdGlvbihuZXcgUlRDU2Vzc2lvbkRlc2NyaXB0aW9uKHt0eXBlOiAnb2ZmZXInLCBzZHA6IHNkcC5yYXd9KSxcbiAgICAgICAgZnVuY3Rpb24oKSB7XG5cbiAgICAgICAgICAgIGlmKHNlbGYuc2lnbmFsaW5nU3RhdGUgPT0gJ2Nsb3NlZCcpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKFwiY3JlYXRlQW5zd2VyIGF0dGVtcHQgb24gY2xvc2VkIHN0YXRlXCIpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgc2VsZi5wZWVyY29ubmVjdGlvbi5jcmVhdGVBbnN3ZXIoXG4gICAgICAgICAgICAgICAgZnVuY3Rpb24obW9kaWZpZWRBbnN3ZXIpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gY2hhbmdlIHZpZGVvIGRpcmVjdGlvbiwgc2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9qaXRzaS9qaXRtZWV0L2lzc3Vlcy80MVxuICAgICAgICAgICAgICAgICAgICBpZiAoc2VsZi5wZW5kaW5nb3AgIT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBzZHAgPSBuZXcgU0RQKG1vZGlmaWVkQW5zd2VyLnNkcCk7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoc2RwLm1lZGlhLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzd2l0Y2goc2VsZi5wZW5kaW5nb3ApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FzZSAnbXV0ZSc6XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZHAubWVkaWFbMV0gPSBzZHAubWVkaWFbMV0ucmVwbGFjZSgnYT1zZW5kcmVjdicsICdhPXJlY3Zvbmx5Jyk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FzZSAndW5tdXRlJzpcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkcC5tZWRpYVsxXSA9IHNkcC5tZWRpYVsxXS5yZXBsYWNlKCdhPXJlY3Zvbmx5JywgJ2E9c2VuZHJlY3YnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZHAucmF3ID0gc2RwLnNlc3Npb24gKyBzZHAubWVkaWEuam9pbignJyk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kaWZpZWRBbnN3ZXIuc2RwID0gc2RwLnJhdztcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYucGVuZGluZ29wID0gbnVsbDtcbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIC8vIEZJWE1FOiBwdXNoaW5nIGRvd24gYW4gYW5zd2VyIHdoaWxlIGljZSBjb25uZWN0aW9uIHN0YXRlXG4gICAgICAgICAgICAgICAgICAgIC8vIGlzIHN0aWxsIGNoZWNraW5nIGlzIGJhZC4uLlxuICAgICAgICAgICAgICAgICAgICAvL2NvbnNvbGUubG9nKHNlbGYucGVlcmNvbm5lY3Rpb24uaWNlQ29ubmVjdGlvblN0YXRlKTtcblxuICAgICAgICAgICAgICAgICAgICAvLyB0cnlpbmcgdG8gd29yayBhcm91bmQgYW5vdGhlciBjaHJvbWUgYnVnXG4gICAgICAgICAgICAgICAgICAgIC8vbW9kaWZpZWRBbnN3ZXIuc2RwID0gbW9kaWZpZWRBbnN3ZXIuc2RwLnJlcGxhY2UoL2E9c2V0dXA6YWN0aXZlL2csICdhPXNldHVwOmFjdHBhc3MnKTtcbiAgICAgICAgICAgICAgICAgICAgc2VsZi5wZWVyY29ubmVjdGlvbi5zZXRMb2NhbERlc2NyaXB0aW9uKG1vZGlmaWVkQW5zd2VyLFxuICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy9jb25zb2xlLmxvZygnbW9kaWZpZWQgc2V0TG9jYWxEZXNjcmlwdGlvbiBvaycpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYuc2V0TG9jYWxEZXNjcmlwdGlvbigpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmKHN1Y2Nlc3NDYWxsYmFjayl7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1Y2Nlc3NDYWxsYmFjaygpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbihlcnJvcikge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ21vZGlmaWVkIHNldExvY2FsRGVzY3JpcHRpb24gZmFpbGVkJywgZXJyb3IpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgZnVuY3Rpb24oZXJyb3IpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5lcnJvcignbW9kaWZpZWQgYW5zd2VyIGZhaWxlZCcsIGVycm9yKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICApO1xuICAgICAgICB9LFxuICAgICAgICBmdW5jdGlvbihlcnJvcikge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcignbW9kaWZ5IGZhaWxlZCcsIGVycm9yKTtcbiAgICAgICAgfVxuICAgICk7XG59O1xuXG4vKipcbiAqIFN3aXRjaGVzIHZpZGVvIHN0cmVhbXMuXG4gKiBAcGFyYW0gbmV3X3N0cmVhbSBuZXcgc3RyZWFtIHRoYXQgd2lsbCBiZSB1c2VkIGFzIHZpZGVvIG9mIHRoaXMgc2Vzc2lvbi5cbiAqIEBwYXJhbSBvbGRTdHJlYW0gb2xkIHZpZGVvIHN0cmVhbSBvZiB0aGlzIHNlc3Npb24uXG4gKiBAcGFyYW0gc3VjY2Vzc19jYWxsYmFjayBjYWxsYmFjayBleGVjdXRlZCBhZnRlciBzdWNjZXNzZnVsIHN0cmVhbSBzd2l0Y2guXG4gKi9cbkppbmdsZVNlc3Npb24ucHJvdG90eXBlLnN3aXRjaFN0cmVhbXMgPSBmdW5jdGlvbiAobmV3X3N0cmVhbSwgb2xkU3RyZWFtLCBzdWNjZXNzX2NhbGxiYWNrKSB7XG5cbiAgICB2YXIgc2VsZiA9IHRoaXM7XG5cbiAgICAvLyBSZW1lbWJlciBTRFAgdG8gZmlndXJlIG91dCBhZGRlZC9yZW1vdmVkIFNTUkNzXG4gICAgdmFyIG9sZFNkcCA9IG51bGw7XG4gICAgaWYoc2VsZi5wZWVyY29ubmVjdGlvbikge1xuICAgICAgICBpZihzZWxmLnBlZXJjb25uZWN0aW9uLmxvY2FsRGVzY3JpcHRpb24pIHtcbiAgICAgICAgICAgIG9sZFNkcCA9IG5ldyBTRFAoc2VsZi5wZWVyY29ubmVjdGlvbi5sb2NhbERlc2NyaXB0aW9uLnNkcCk7XG4gICAgICAgIH1cbiAgICAgICAgc2VsZi5wZWVyY29ubmVjdGlvbi5yZW1vdmVTdHJlYW0ob2xkU3RyZWFtLCB0cnVlKTtcbiAgICAgICAgc2VsZi5wZWVyY29ubmVjdGlvbi5hZGRTdHJlYW0obmV3X3N0cmVhbSk7XG4gICAgfVxuXG4gICAgUlRDLnN3aXRjaFZpZGVvU3RyZWFtcyhuZXdfc3RyZWFtLCBvbGRTdHJlYW0pO1xuXG4gICAgLy8gQ29uZmVyZW5jZSBpcyBub3QgYWN0aXZlXG4gICAgaWYoIW9sZFNkcCB8fCAhc2VsZi5wZWVyY29ubmVjdGlvbikge1xuICAgICAgICBzdWNjZXNzX2NhbGxiYWNrKCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBzZWxmLnN3aXRjaHN0cmVhbXMgPSB0cnVlO1xuICAgIHNlbGYubW9kaWZ5U291cmNlcyhmdW5jdGlvbigpIHtcbiAgICAgICAgY29uc29sZS5sb2coJ21vZGlmeSBzb3VyY2VzIGRvbmUnKTtcblxuICAgICAgICBzdWNjZXNzX2NhbGxiYWNrKCk7XG5cbiAgICAgICAgdmFyIG5ld1NkcCA9IG5ldyBTRFAoc2VsZi5wZWVyY29ubmVjdGlvbi5sb2NhbERlc2NyaXB0aW9uLnNkcCk7XG4gICAgICAgIGNvbnNvbGUubG9nKFwiU0RQc1wiLCBvbGRTZHAsIG5ld1NkcCk7XG4gICAgICAgIHNlbGYubm90aWZ5TXlTU1JDVXBkYXRlKG9sZFNkcCwgbmV3U2RwKTtcbiAgICB9KTtcbn07XG5cbi8qKlxuICogRmlndXJlcyBvdXQgYWRkZWQvcmVtb3ZlZCBzc3JjcyBhbmQgc2VuZCB1cGRhdGUgSVFzLlxuICogQHBhcmFtIG9sZF9zZHAgU0RQIG9iamVjdCBmb3Igb2xkIGRlc2NyaXB0aW9uLlxuICogQHBhcmFtIG5ld19zZHAgU0RQIG9iamVjdCBmb3IgbmV3IGRlc2NyaXB0aW9uLlxuICovXG5KaW5nbGVTZXNzaW9uLnByb3RvdHlwZS5ub3RpZnlNeVNTUkNVcGRhdGUgPSBmdW5jdGlvbiAob2xkX3NkcCwgbmV3X3NkcCkge1xuXG4gICAgaWYgKCEodGhpcy5wZWVyY29ubmVjdGlvbi5zaWduYWxpbmdTdGF0ZSA9PSAnc3RhYmxlJyAmJlxuICAgICAgICB0aGlzLnBlZXJjb25uZWN0aW9uLmljZUNvbm5lY3Rpb25TdGF0ZSA9PSAnY29ubmVjdGVkJykpe1xuICAgICAgICBjb25zb2xlLmxvZyhcIlRvbyBlYXJseSB0byBzZW5kIHVwZGF0ZXNcIik7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBzZW5kIHNvdXJjZS1yZW1vdmUgSVEuXG4gICAgc2RwRGlmZmVyID0gbmV3IFNEUERpZmZlcihuZXdfc2RwLCBvbGRfc2RwKTtcbiAgICB2YXIgcmVtb3ZlID0gJGlxKHt0bzogdGhpcy5wZWVyamlkLCB0eXBlOiAnc2V0J30pXG4gICAgICAgIC5jKCdqaW5nbGUnLCB7XG4gICAgICAgICAgICB4bWxuczogJ3Vybjp4bXBwOmppbmdsZToxJyxcbiAgICAgICAgICAgIGFjdGlvbjogJ3NvdXJjZS1yZW1vdmUnLFxuICAgICAgICAgICAgaW5pdGlhdG9yOiB0aGlzLmluaXRpYXRvcixcbiAgICAgICAgICAgIHNpZDogdGhpcy5zaWRcbiAgICAgICAgfVxuICAgICk7XG4gICAgdmFyIHJlbW92ZWQgPSBzZHBEaWZmZXIudG9KaW5nbGUocmVtb3ZlKTtcbiAgICBpZiAocmVtb3ZlZCkge1xuICAgICAgICB0aGlzLmNvbm5lY3Rpb24uc2VuZElRKHJlbW92ZSxcbiAgICAgICAgICAgIGZ1bmN0aW9uIChyZXMpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmluZm8oJ2dvdCByZW1vdmUgcmVzdWx0JywgcmVzKTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBmdW5jdGlvbiAoZXJyKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS5lcnJvcignZ290IHJlbW92ZSBlcnJvcicsIGVycik7XG4gICAgICAgICAgICB9XG4gICAgICAgICk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgY29uc29sZS5sb2coJ3JlbW92YWwgbm90IG5lY2Vzc2FyeScpO1xuICAgIH1cblxuICAgIC8vIHNlbmQgc291cmNlLWFkZCBJUS5cbiAgICB2YXIgc2RwRGlmZmVyID0gbmV3IFNEUERpZmZlcihvbGRfc2RwLCBuZXdfc2RwKTtcbiAgICB2YXIgYWRkID0gJGlxKHt0bzogdGhpcy5wZWVyamlkLCB0eXBlOiAnc2V0J30pXG4gICAgICAgIC5jKCdqaW5nbGUnLCB7XG4gICAgICAgICAgICB4bWxuczogJ3Vybjp4bXBwOmppbmdsZToxJyxcbiAgICAgICAgICAgIGFjdGlvbjogJ3NvdXJjZS1hZGQnLFxuICAgICAgICAgICAgaW5pdGlhdG9yOiB0aGlzLmluaXRpYXRvcixcbiAgICAgICAgICAgIHNpZDogdGhpcy5zaWRcbiAgICAgICAgfVxuICAgICk7XG4gICAgdmFyIGFkZGVkID0gc2RwRGlmZmVyLnRvSmluZ2xlKGFkZCk7XG4gICAgaWYgKGFkZGVkKSB7XG4gICAgICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kSVEoYWRkLFxuICAgICAgICAgICAgZnVuY3Rpb24gKHJlcykge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUuaW5mbygnZ290IGFkZCByZXN1bHQnLCByZXMpO1xuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGZ1bmN0aW9uIChlcnIpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKCdnb3QgYWRkIGVycm9yJywgZXJyKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBjb25zb2xlLmxvZygnYWRkaXRpb24gbm90IG5lY2Vzc2FyeScpO1xuICAgIH1cbn07XG5cbi8qKlxuICogRGV0ZXJtaW5lcyB3aGV0aGVyIHRoZSAobG9jYWwpIHZpZGVvIGlzIG11dGUgaS5lLiBhbGwgdmlkZW8gdHJhY2tzIGFyZVxuICogZGlzYWJsZWQuXG4gKlxuICogQHJldHVybiA8dHQ+dHJ1ZTwvdHQ+IGlmIHRoZSAobG9jYWwpIHZpZGVvIGlzIG11dGUgaS5lLiBhbGwgdmlkZW8gdHJhY2tzIGFyZVxuICogZGlzYWJsZWQ7IG90aGVyd2lzZSwgPHR0PmZhbHNlPC90dD5cbiAqL1xuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuaXNWaWRlb011dGUgPSBmdW5jdGlvbiAoKSB7XG4gICAgdmFyIHRyYWNrcyA9IFJUQy5sb2NhbFZpZGVvLmdldFZpZGVvVHJhY2tzKCk7XG4gICAgdmFyIG11dGUgPSB0cnVlO1xuXG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0cmFja3MubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgaWYgKHRyYWNrc1tpXS5lbmFibGVkKSB7XG4gICAgICAgICAgICBtdXRlID0gZmFsc2U7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gbXV0ZTtcbn07XG5cbi8qKlxuICogTXV0ZXMvdW5tdXRlcyB0aGUgKGxvY2FsKSB2aWRlbyBpLmUuIGVuYWJsZXMvZGlzYWJsZXMgYWxsIHZpZGVvIHRyYWNrcy5cbiAqXG4gKiBAcGFyYW0gbXV0ZSA8dHQ+dHJ1ZTwvdHQ+IHRvIG11dGUgdGhlIChsb2NhbCkgdmlkZW8gaS5lLiB0byBkaXNhYmxlIGFsbCB2aWRlb1xuICogdHJhY2tzOyBvdGhlcndpc2UsIDx0dD5mYWxzZTwvdHQ+XG4gKiBAcGFyYW0gY2FsbGJhY2sgYSBmdW5jdGlvbiB0byBiZSBpbnZva2VkIHdpdGggPHR0Pm11dGU8L3R0PiBhZnRlciBhbGwgdmlkZW9cbiAqIHRyYWNrcyBoYXZlIGJlZW4gZW5hYmxlZC9kaXNhYmxlZC4gVGhlIGZ1bmN0aW9uIG1heSwgb3B0aW9uYWxseSwgcmV0dXJuXG4gKiBhbm90aGVyIGZ1bmN0aW9uIHdoaWNoIGlzIHRvIGJlIGludm9rZWQgYWZ0ZXIgdGhlIHdob2xlIG11dGUvdW5tdXRlIG9wZXJhdGlvblxuICogaGFzIGNvbXBsZXRlZCBzdWNjZXNzZnVsbHkuXG4gKiBAcGFyYW0gb3B0aW9ucyBhbiBvYmplY3Qgd2hpY2ggc3BlY2lmaWVzIG9wdGlvbmFsIGFyZ3VtZW50cyBzdWNoIGFzIHRoZVxuICogPHR0PmJvb2xlYW48L3R0PiBrZXkgPHR0PmJ5VXNlcjwvdHQ+IHdpdGggZGVmYXVsdCB2YWx1ZSA8dHQ+dHJ1ZTwvdHQ+IHdoaWNoXG4gKiBzcGVjaWZpZXMgd2hldGhlciB0aGUgbWV0aG9kIHdhcyBpbml0aWF0ZWQgaW4gcmVzcG9uc2UgdG8gYSB1c2VyIGNvbW1hbmQgKGluXG4gKiBjb250cmFzdCB0byBhbiBhdXRvbWF0aWMgZGVjaXNpb24gbWFkZSBieSB0aGUgYXBwbGljYXRpb24gbG9naWMpXG4gKi9cbkppbmdsZVNlc3Npb24ucHJvdG90eXBlLnNldFZpZGVvTXV0ZSA9IGZ1bmN0aW9uIChtdXRlLCBjYWxsYmFjaywgb3B0aW9ucykge1xuICAgIHZhciBieVVzZXI7XG5cbiAgICBpZiAob3B0aW9ucykge1xuICAgICAgICBieVVzZXIgPSBvcHRpb25zLmJ5VXNlcjtcbiAgICAgICAgaWYgKHR5cGVvZiBieVVzZXIgPT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgICAgICBieVVzZXIgPSB0cnVlO1xuICAgICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgICAgYnlVc2VyID0gdHJ1ZTtcbiAgICB9XG4gICAgLy8gVGhlIHVzZXIncyBjb21tYW5kIHRvIG11dGUgdGhlIChsb2NhbCkgdmlkZW8gdGFrZXMgcHJlY2VkZW5jZSBvdmVyIGFueVxuICAgIC8vIGF1dG9tYXRpYyBkZWNpc2lvbiBtYWRlIGJ5IHRoZSBhcHBsaWNhdGlvbiBsb2dpYy5cbiAgICBpZiAoYnlVc2VyKSB7XG4gICAgICAgIHRoaXMudmlkZW9NdXRlQnlVc2VyID0gbXV0ZTtcbiAgICB9IGVsc2UgaWYgKHRoaXMudmlkZW9NdXRlQnlVc2VyKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgdmFyIGxvY2FsQ2FsbGJhY2sgPSBmdW5jdGlvbiAobXV0ZSkge1xuICAgICAgICBzZWxmLmNvbm5lY3Rpb24uZW11Yy5hZGRWaWRlb0luZm9Ub1ByZXNlbmNlKG11dGUpO1xuICAgICAgICBzZWxmLmNvbm5lY3Rpb24uZW11Yy5zZW5kUHJlc2VuY2UoKTtcbiAgICAgICAgcmV0dXJuIGNhbGxiYWNrKG11dGUpXG4gICAgfTtcblxuICAgIGlmIChtdXRlID09IFJUQy5sb2NhbFZpZGVvLmlzTXV0ZWQoKSlcbiAgICB7XG4gICAgICAgIC8vIEV2ZW4gaWYgbm8gY2hhbmdlIG9jY3VycywgdGhlIHNwZWNpZmllZCBjYWxsYmFjayBpcyB0byBiZSBleGVjdXRlZC5cbiAgICAgICAgLy8gVGhlIHNwZWNpZmllZCBjYWxsYmFjayBtYXksIG9wdGlvbmFsbHksIHJldHVybiBhIHN1Y2Nlc3NDYWxsYmFja1xuICAgICAgICAvLyB3aGljaCBpcyB0byBiZSBleGVjdXRlZCBhcyB3ZWxsLlxuICAgICAgICB2YXIgc3VjY2Vzc0NhbGxiYWNrID0gbG9jYWxDYWxsYmFjayhtdXRlKTtcblxuICAgICAgICBpZiAoc3VjY2Vzc0NhbGxiYWNrKSB7XG4gICAgICAgICAgICBzdWNjZXNzQ2FsbGJhY2soKTtcbiAgICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAgIFJUQy5sb2NhbFZpZGVvLnNldE11dGUoIW11dGUpO1xuXG4gICAgICAgIHRoaXMuaGFyZE11dGVWaWRlbyhtdXRlKTtcblxuICAgICAgICB0aGlzLm1vZGlmeVNvdXJjZXMobG9jYWxDYWxsYmFjayhtdXRlKSk7XG4gICAgfVxufTtcblxuLy8gU0RQLWJhc2VkIG11dGUgYnkgZ29pbmcgcmVjdm9ubHkvc2VuZHJlY3Zcbi8vIEZJWE1FOiBzaG91bGQgcHJvYmFibHkgYmxhY2sgb3V0IHRoZSBzY3JlZW4gYXMgd2VsbFxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUudG9nZ2xlVmlkZW9NdXRlID0gZnVuY3Rpb24gKGNhbGxiYWNrKSB7XG4gICAgdGhpcy5zZXJ2aWNlLnNldFZpZGVvTXV0ZShSVEMubG9jYWxWaWRlby5pc011dGVkKCksIGNhbGxiYWNrKTtcbn07XG5cbkppbmdsZVNlc3Npb24ucHJvdG90eXBlLmhhcmRNdXRlVmlkZW8gPSBmdW5jdGlvbiAobXV0ZWQpIHtcbiAgICB0aGlzLnBlbmRpbmdvcCA9IG11dGVkID8gJ211dGUnIDogJ3VubXV0ZSc7XG59O1xuXG5KaW5nbGVTZXNzaW9uLnByb3RvdHlwZS5zZW5kTXV0ZSA9IGZ1bmN0aW9uIChtdXRlZCwgY29udGVudCkge1xuICAgIHZhciBpbmZvID0gJGlxKHt0bzogdGhpcy5wZWVyamlkLFxuICAgICAgICB0eXBlOiAnc2V0J30pXG4gICAgICAgIC5jKCdqaW5nbGUnLCB7eG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6MScsXG4gICAgICAgICAgICBhY3Rpb246ICdzZXNzaW9uLWluZm8nLFxuICAgICAgICAgICAgaW5pdGlhdG9yOiB0aGlzLmluaXRpYXRvcixcbiAgICAgICAgICAgIHNpZDogdGhpcy5zaWQgfSk7XG4gICAgaW5mby5jKG11dGVkID8gJ211dGUnIDogJ3VubXV0ZScsIHt4bWxuczogJ3Vybjp4bXBwOmppbmdsZTphcHBzOnJ0cDppbmZvOjEnfSk7XG4gICAgaW5mby5hdHRycyh7J2NyZWF0b3InOiB0aGlzLm1lID09IHRoaXMuaW5pdGlhdG9yID8gJ2NyZWF0b3InIDogJ3Jlc3BvbmRlcid9KTtcbiAgICBpZiAoY29udGVudCkge1xuICAgICAgICBpbmZvLmF0dHJzKHsnbmFtZSc6IGNvbnRlbnR9KTtcbiAgICB9XG4gICAgdGhpcy5jb25uZWN0aW9uLnNlbmQoaW5mbyk7XG59O1xuXG5KaW5nbGVTZXNzaW9uLnByb3RvdHlwZS5zZW5kUmluZ2luZyA9IGZ1bmN0aW9uICgpIHtcbiAgICB2YXIgaW5mbyA9ICRpcSh7dG86IHRoaXMucGVlcmppZCxcbiAgICAgICAgdHlwZTogJ3NldCd9KVxuICAgICAgICAuYygnamluZ2xlJywge3htbG5zOiAndXJuOnhtcHA6amluZ2xlOjEnLFxuICAgICAgICAgICAgYWN0aW9uOiAnc2Vzc2lvbi1pbmZvJyxcbiAgICAgICAgICAgIGluaXRpYXRvcjogdGhpcy5pbml0aWF0b3IsXG4gICAgICAgICAgICBzaWQ6IHRoaXMuc2lkIH0pO1xuICAgIGluZm8uYygncmluZ2luZycsIHt4bWxuczogJ3Vybjp4bXBwOmppbmdsZTphcHBzOnJ0cDppbmZvOjEnfSk7XG4gICAgdGhpcy5jb25uZWN0aW9uLnNlbmQoaW5mbyk7XG59O1xuXG5KaW5nbGVTZXNzaW9uLnByb3RvdHlwZS5nZXRTdGF0cyA9IGZ1bmN0aW9uIChpbnRlcnZhbCkge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB2YXIgcmVjdiA9IHthdWRpbzogMCwgdmlkZW86IDB9O1xuICAgIHZhciBsb3N0ID0ge2F1ZGlvOiAwLCB2aWRlbzogMH07XG4gICAgdmFyIGxhc3RyZWN2ID0ge2F1ZGlvOiAwLCB2aWRlbzogMH07XG4gICAgdmFyIGxhc3Rsb3N0ID0ge2F1ZGlvOiAwLCB2aWRlbzogMH07XG4gICAgdmFyIGxvc3MgPSB7YXVkaW86IDAsIHZpZGVvOiAwfTtcbiAgICB2YXIgZGVsdGEgPSB7YXVkaW86IDAsIHZpZGVvOiAwfTtcbiAgICB0aGlzLnN0YXRzaW50ZXJ2YWwgPSB3aW5kb3cuc2V0SW50ZXJ2YWwoZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAoc2VsZiAmJiBzZWxmLnBlZXJjb25uZWN0aW9uICYmIHNlbGYucGVlcmNvbm5lY3Rpb24uZ2V0U3RhdHMpIHtcbiAgICAgICAgICAgIHNlbGYucGVlcmNvbm5lY3Rpb24uZ2V0U3RhdHMoZnVuY3Rpb24gKHN0YXRzKSB7XG4gICAgICAgICAgICAgICAgdmFyIHJlc3VsdHMgPSBzdGF0cy5yZXN1bHQoKTtcbiAgICAgICAgICAgICAgICAvLyBUT0RPOiB0aGVyZSBhcmUgc28gbXVjaCBzdGF0aXN0aWNzIHlvdSBjYW4gZ2V0IGZyb20gdGhpcy4uXG4gICAgICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCByZXN1bHRzLmxlbmd0aDsgKytpKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHRzW2ldLnR5cGUgPT0gJ3NzcmMnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgcGFja2V0c3JlY3YgPSByZXN1bHRzW2ldLnN0YXQoJ3BhY2tldHNSZWNlaXZlZCcpO1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIHBhY2tldHNsb3N0ID0gcmVzdWx0c1tpXS5zdGF0KCdwYWNrZXRzTG9zdCcpO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHBhY2tldHNyZWN2ICYmIHBhY2tldHNsb3N0KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFja2V0c3JlY3YgPSBwYXJzZUludChwYWNrZXRzcmVjdiwgMTApO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhY2tldHNsb3N0ID0gcGFyc2VJbnQocGFja2V0c2xvc3QsIDEwKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChyZXN1bHRzW2ldLnN0YXQoJ2dvb2dGcmFtZVJhdGVSZWNlaXZlZCcpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3Rsb3N0LnZpZGVvID0gbG9zdC52aWRlbztcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdHJlY3YudmlkZW8gPSByZWN2LnZpZGVvO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWN2LnZpZGVvID0gcGFja2V0c3JlY3Y7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvc3QudmlkZW8gPSBwYWNrZXRzbG9zdDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0bG9zdC5hdWRpbyA9IGxvc3QuYXVkaW87XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RyZWN2LmF1ZGlvID0gcmVjdi5hdWRpbztcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVjdi5hdWRpbyA9IHBhY2tldHNyZWN2O1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb3N0LmF1ZGlvID0gcGFja2V0c2xvc3Q7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGRlbHRhLmF1ZGlvID0gcmVjdi5hdWRpbyAtIGxhc3RyZWN2LmF1ZGlvO1xuICAgICAgICAgICAgICAgIGRlbHRhLnZpZGVvID0gcmVjdi52aWRlbyAtIGxhc3RyZWN2LnZpZGVvO1xuICAgICAgICAgICAgICAgIGxvc3MuYXVkaW8gPSAoZGVsdGEuYXVkaW8gPiAwKSA/IE1hdGguY2VpbCgxMDAgKiAobG9zdC5hdWRpbyAtIGxhc3Rsb3N0LmF1ZGlvKSAvIGRlbHRhLmF1ZGlvKSA6IDA7XG4gICAgICAgICAgICAgICAgbG9zcy52aWRlbyA9IChkZWx0YS52aWRlbyA+IDApID8gTWF0aC5jZWlsKDEwMCAqIChsb3N0LnZpZGVvIC0gbGFzdGxvc3QudmlkZW8pIC8gZGVsdGEudmlkZW8pIDogMDtcbiAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdwYWNrZXRsb3NzLmppbmdsZScsIFtzZWxmLnNpZCwgbG9zc10pO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICB9LCBpbnRlcnZhbCB8fCAzMDAwKTtcbiAgICByZXR1cm4gdGhpcy5zdGF0c2ludGVydmFsO1xufTtcblxuSmluZ2xlU2Vzc2lvbi5vbkppbmdsZUVycm9yID0gZnVuY3Rpb24gKHNlc3Npb24sIGVycm9yKVxue1xuICAgIGNvbnNvbGUuZXJyb3IoXCJKaW5nbGUgZXJyb3JcIiwgZXJyb3IpO1xufVxuXG5KaW5nbGVTZXNzaW9uLm9uSmluZ2xlRmF0YWxFcnJvciA9IGZ1bmN0aW9uIChzZXNzaW9uLCBlcnJvcilcbntcbiAgICB0aGlzLnNlcnZpY2Uuc2Vzc2lvblRlcm1pbmF0ZWQgPSB0cnVlO1xuICAgIGNvbm5lY3Rpb24uZW11Yy5kb0xlYXZlKCk7XG4gICAgVUkubWVzc2FnZUhhbmRsZXIuc2hvd0Vycm9yKCAgXCJTb3JyeVwiLFxuICAgICAgICBcIkludGVybmFsIGFwcGxpY2F0aW9uIGVycm9yW3NldFJlbW90ZURlc2NyaXB0aW9uXVwiKTtcbn1cblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUuc2V0TG9jYWxEZXNjcmlwdGlvbiA9IGZ1bmN0aW9uICgpIHtcbiAgICAvLyBwdXQgb3VyIHNzcmNzIGludG8gcHJlc2VuY2Ugc28gb3RoZXIgY2xpZW50cyBjYW4gaWRlbnRpZnkgb3VyIHN0cmVhbVxuICAgIHZhciBuZXdzc3JjcyA9IFtdO1xuICAgIHZhciBtZWRpYSA9IHNpbXVsY2FzdC5wYXJzZU1lZGlhKHRoaXMucGVlcmNvbm5lY3Rpb24ubG9jYWxEZXNjcmlwdGlvbik7XG4gICAgbWVkaWEuZm9yRWFjaChmdW5jdGlvbiAobWVkaWEpIHtcblxuICAgICAgICBpZihPYmplY3Qua2V5cyhtZWRpYS5zb3VyY2VzKS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAvLyBUT0RPKGdwKSBtYXliZSBleGNsdWRlIEZJRCBzdHJlYW1zP1xuICAgICAgICAgICAgT2JqZWN0LmtleXMobWVkaWEuc291cmNlcykuZm9yRWFjaChmdW5jdGlvbiAoc3NyYykge1xuICAgICAgICAgICAgICAgIG5ld3NzcmNzLnB1c2goe1xuICAgICAgICAgICAgICAgICAgICAnc3NyYyc6IHNzcmMsXG4gICAgICAgICAgICAgICAgICAgICd0eXBlJzogbWVkaWEudHlwZSxcbiAgICAgICAgICAgICAgICAgICAgJ2RpcmVjdGlvbic6IG1lZGlhLmRpcmVjdGlvblxuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZih0aGlzLmxvY2FsU3RyZWFtc1NTUkMgJiYgdGhpcy5sb2NhbFN0cmVhbXNTU1JDW21lZGlhLnR5cGVdKVxuICAgICAgICB7XG4gICAgICAgICAgICBuZXdzc3Jjcy5wdXNoKHtcbiAgICAgICAgICAgICAgICAnc3NyYyc6IHRoaXMubG9jYWxTdHJlYW1zU1NSQ1ttZWRpYS50eXBlXSxcbiAgICAgICAgICAgICAgICAndHlwZSc6IG1lZGlhLnR5cGUsXG4gICAgICAgICAgICAgICAgJ2RpcmVjdGlvbic6IG1lZGlhLmRpcmVjdGlvblxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgIH0pO1xuXG4gICAgY29uc29sZS5sb2coJ25ldyBzc3JjcycsIG5ld3NzcmNzKTtcblxuICAgIC8vIEhhdmUgdG8gY2xlYXIgcHJlc2VuY2UgbWFwIHRvIGdldCByaWQgb2YgcmVtb3ZlZCBzdHJlYW1zXG4gICAgdGhpcy5jb25uZWN0aW9uLmVtdWMuY2xlYXJQcmVzZW5jZU1lZGlhKCk7XG5cbiAgICBpZiAobmV3c3NyY3MubGVuZ3RoID4gMCkge1xuICAgICAgICBmb3IgKHZhciBpID0gMTsgaSA8PSBuZXdzc3Jjcy5sZW5ndGg7IGkgKyspIHtcbiAgICAgICAgICAgIC8vIENoYW5nZSB2aWRlbyB0eXBlIHRvIHNjcmVlblxuICAgICAgICAgICAgaWYgKG5ld3NzcmNzW2ktMV0udHlwZSA9PT0gJ3ZpZGVvJyAmJiBkZXNrdG9wc2hhcmluZy5pc1VzaW5nU2NyZWVuU3RyZWFtKCkpIHtcbiAgICAgICAgICAgICAgICBuZXdzc3Jjc1tpLTFdLnR5cGUgPSAnc2NyZWVuJztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5lbXVjLmFkZE1lZGlhVG9QcmVzZW5jZShpLFxuICAgICAgICAgICAgICAgIG5ld3NzcmNzW2ktMV0udHlwZSwgbmV3c3NyY3NbaS0xXS5zc3JjLCBuZXdzc3Jjc1tpLTFdLmRpcmVjdGlvbik7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLmNvbm5lY3Rpb24uZW11Yy5zZW5kUHJlc2VuY2UoKTtcbiAgICB9XG59XG5cbi8vIGFuIGF0dGVtcHQgdG8gd29yayBhcm91bmQgaHR0cHM6Ly9naXRodWIuY29tL2ppdHNpL2ppdG1lZXQvaXNzdWVzLzMyXG5mdW5jdGlvbiBzZW5kS2V5ZnJhbWUocGMpIHtcbiAgICBjb25zb2xlLmxvZygnc2VuZGtleWZyYW1lJywgcGMuaWNlQ29ubmVjdGlvblN0YXRlKTtcbiAgICBpZiAocGMuaWNlQ29ubmVjdGlvblN0YXRlICE9PSAnY29ubmVjdGVkJykgcmV0dXJuOyAvLyBzYWZlLi4uXG4gICAgcGMuc2V0UmVtb3RlRGVzY3JpcHRpb24oXG4gICAgICAgIHBjLnJlbW90ZURlc2NyaXB0aW9uLFxuICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBwYy5jcmVhdGVBbnN3ZXIoXG4gICAgICAgICAgICAgICAgZnVuY3Rpb24gKG1vZGlmaWVkQW5zd2VyKSB7XG4gICAgICAgICAgICAgICAgICAgIHBjLnNldExvY2FsRGVzY3JpcHRpb24oXG4gICAgICAgICAgICAgICAgICAgICAgICBtb2RpZmllZEFuc3dlcixcbiAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBub29wXG4gICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gKGVycm9yKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uc29sZS5sb2coJ3RyaWdnZXJLZXlmcmFtZSBzZXRMb2NhbERlc2NyaXB0aW9uIGZhaWxlZCcsIGVycm9yKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBVSS5tZXNzYWdlSGFuZGxlci5zaG93RXJyb3IoKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChlcnJvcikge1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygndHJpZ2dlcktleWZyYW1lIGNyZWF0ZUFuc3dlciBmYWlsZWQnLCBlcnJvcik7XG4gICAgICAgICAgICAgICAgICAgIFVJLm1lc3NhZ2VIYW5kbGVyLnNob3dFcnJvcigpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICk7XG4gICAgICAgIH0sXG4gICAgICAgIGZ1bmN0aW9uIChlcnJvcikge1xuICAgICAgICAgICAgY29uc29sZS5sb2coJ3RyaWdnZXJLZXlmcmFtZSBzZXRSZW1vdGVEZXNjcmlwdGlvbiBmYWlsZWQnLCBlcnJvcik7XG4gICAgICAgICAgICBVSS5tZXNzYWdlSGFuZGxlci5zaG93RXJyb3IoKTtcbiAgICAgICAgfVxuICAgICk7XG59XG5cblxuSmluZ2xlU2Vzc2lvbi5wcm90b3R5cGUucmVtb3RlU3RyZWFtQWRkZWQgPSBmdW5jdGlvbiAoZGF0YSkge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB2YXIgdGhlc3NyYztcblxuICAgIC8vIGxvb2sgdXAgYW4gYXNzb2NpYXRlZCBKSUQgZm9yIGEgc3RyZWFtIGlkXG4gICAgaWYgKGRhdGEuc3RyZWFtLmlkICYmIGRhdGEuc3RyZWFtLmlkLmluZGV4T2YoJ21peGVkbXNsYWJlbCcpID09PSAtMSkge1xuICAgICAgICAvLyBsb29rIG9ubHkgYXQgYT1zc3JjOiBhbmQgX25vdF8gYXQgYT1zc3JjLWdyb3VwOiBsaW5lc1xuXG4gICAgICAgIHZhciBzc3JjbGluZXNcbiAgICAgICAgICAgID0gU0RQVXRpbC5maW5kX2xpbmVzKHRoaXMucGVlcmNvbm5lY3Rpb24ucmVtb3RlRGVzY3JpcHRpb24uc2RwLCAnYT1zc3JjOicpO1xuICAgICAgICBzc3JjbGluZXMgPSBzc3JjbGluZXMuZmlsdGVyKGZ1bmN0aW9uIChsaW5lKSB7XG4gICAgICAgICAgICAvLyBOT1RFKGdwKSBwcmV2aW91c2x5IHdlIGZpbHRlcmVkIG9uIHRoZSBtc2xhYmVsLCBidXQgdGhhdCBwcm9wZXJ0eVxuICAgICAgICAgICAgLy8gaXMgbm90IGFsd2F5cyBwcmVzZW50LlxuICAgICAgICAgICAgLy8gcmV0dXJuIGxpbmUuaW5kZXhPZignbXNsYWJlbDonICsgZGF0YS5zdHJlYW0ubGFiZWwpICE9PSAtMTtcblxuICAgICAgICAgICAgcmV0dXJuICgobGluZS5pbmRleE9mKCdtc2lkOicgKyBkYXRhLnN0cmVhbS5pZCkgIT09IC0xKSk7XG4gICAgICAgIH0pO1xuICAgICAgICBpZiAoc3NyY2xpbmVzLmxlbmd0aCkge1xuICAgICAgICAgICAgdGhlc3NyYyA9IHNzcmNsaW5lc1swXS5zdWJzdHJpbmcoNykuc3BsaXQoJyAnKVswXTtcblxuICAgICAgICAgICAgLy8gV2Ugc2lnbmFsIG91ciBzdHJlYW1zICh0aHJvdWdoIEppbmdsZSB0byB0aGUgZm9jdXMpIGJlZm9yZSB3ZSBzZXRcbiAgICAgICAgICAgIC8vIG91ciBwcmVzZW5jZSAodGhyb3VnaCB3aGljaCBwZWVycyBhc3NvY2lhdGUgcmVtb3RlIHN0cmVhbXMgdG9cbiAgICAgICAgICAgIC8vIGppZHMpLiBTbywgaXQgbWlnaHQgYXJyaXZlIHRoYXQgYSByZW1vdGUgc3RyZWFtIGlzIGFkZGVkIGJ1dFxuICAgICAgICAgICAgLy8gc3NyYzJqaWQgaXMgbm90IHlldCB1cGRhdGVkIGFuZCB0aHVzIGRhdGEucGVlcmppZCBjYW5ub3QgYmVcbiAgICAgICAgICAgIC8vIHN1Y2Nlc3NmdWxseSBzZXQuIEhlcmUgd2Ugd2FpdCBmb3IgdXAgdG8gYSBzZWNvbmQgZm9yIHRoZVxuICAgICAgICAgICAgLy8gcHJlc2VuY2UgdG8gYXJyaXZlLlxuXG4gICAgICAgICAgICBpZiAoIXNzcmMyamlkW3RoZXNzcmNdKSB7XG4gICAgICAgICAgICAgICAgLy8gVE9ETyhncCkgbGltaXQgd2FpdCBkdXJhdGlvbiB0byAxIHNlYy5cbiAgICAgICAgICAgICAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uKGQpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgc2VsZi5yZW1vdGVTdHJlYW1BZGRlZChkKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH0oZGF0YSksIDI1MCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBvayB0byBvdmVyd3JpdGUgdGhlIG9uZSBmcm9tIGZvY3VzPyBtaWdodCBzYXZlIHdvcmsgaW4gY29saWJyaS5qc1xuICAgICAgICAgICAgY29uc29sZS5sb2coJ2Fzc29jaWF0ZWQgamlkJywgc3NyYzJqaWRbdGhlc3NyY10sIGRhdGEucGVlcmppZCk7XG4gICAgICAgICAgICBpZiAoc3NyYzJqaWRbdGhlc3NyY10pIHtcbiAgICAgICAgICAgICAgICBkYXRhLnBlZXJqaWQgPSBzc3JjMmppZFt0aGVzc3JjXTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vVE9ETzogdGhpcyBjb2RlIHNob3VsZCBiZSByZW1vdmVkIHdoZW4gZmlyZWZveCBpbXBsZW1lbnQgbXVsdGlzdHJlYW0gc3VwcG9ydFxuICAgIGlmKFJUQy5nZXRCcm93c2VyVHlwZSgpID09IFJUQ0Jyb3dzZXJUeXBlLlJUQ19CUk9XU0VSX0ZJUkVGT1gpXG4gICAge1xuICAgICAgICBpZigobm90UmVjZWl2ZWRTU1JDcy5sZW5ndGggPT0gMCkgfHxcbiAgICAgICAgICAgICFzc3JjMmppZFtub3RSZWNlaXZlZFNTUkNzW25vdFJlY2VpdmVkU1NSQ3MubGVuZ3RoIC0gMV1dKVxuICAgICAgICB7XG4gICAgICAgICAgICAvLyBUT0RPKGdwKSBsaW1pdCB3YWl0IGR1cmF0aW9uIHRvIDEgc2VjLlxuICAgICAgICAgICAgc2V0VGltZW91dChmdW5jdGlvbihkKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgICAgICAgICBzZWxmLnJlbW90ZVN0cmVhbUFkZGVkKGQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0oZGF0YSksIDI1MCk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB0aGVzc3JjID0gbm90UmVjZWl2ZWRTU1JDcy5wb3AoKTtcbiAgICAgICAgaWYgKHNzcmMyamlkW3RoZXNzcmNdKSB7XG4gICAgICAgICAgICBkYXRhLnBlZXJqaWQgPSBzc3JjMmppZFt0aGVzc3JjXTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIFJUQy5jcmVhdGVSZW1vdGVTdHJlYW0oZGF0YSwgdGhpcy5zaWQsIHRoZXNzcmMpO1xuXG4gICAgdmFyIGlzVmlkZW8gPSBkYXRhLnN0cmVhbS5nZXRWaWRlb1RyYWNrcygpLmxlbmd0aCA+IDA7XG4gICAgLy8gYW4gYXR0ZW1wdCB0byB3b3JrIGFyb3VuZCBodHRwczovL2dpdGh1Yi5jb20vaml0c2kvaml0bWVldC9pc3N1ZXMvMzJcbiAgICBpZiAoaXNWaWRlbyAmJlxuICAgICAgICBkYXRhLnBlZXJqaWQgJiYgdGhpcy5wZWVyamlkID09PSBkYXRhLnBlZXJqaWQgJiZcbiAgICAgICAgZGF0YS5zdHJlYW0uZ2V0VmlkZW9UcmFja3MoKS5sZW5ndGggPT09IDAgJiZcbiAgICAgICAgUlRDLmxvY2FsVmlkZW8uZ2V0VHJhY2tzKCkubGVuZ3RoID4gMCkge1xuICAgICAgICB3aW5kb3cuc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBzZW5kS2V5ZnJhbWUoc2VsZi5wZWVyY29ubmVjdGlvbik7XG4gICAgICAgIH0sIDMwMDApO1xuICAgIH1cbn1cblxubW9kdWxlLmV4cG9ydHMgPSBKaW5nbGVTZXNzaW9uOyIsIi8qIGpzaGludCAtVzExNyAqL1xudmFyIFNEUFV0aWwgPSByZXF1aXJlKFwiLi9TRFBVdGlsXCIpO1xuXG4vLyBTRFAgU1RVRkZcbmZ1bmN0aW9uIFNEUChzZHApIHtcbiAgICB0aGlzLm1lZGlhID0gc2RwLnNwbGl0KCdcXHJcXG5tPScpO1xuICAgIGZvciAodmFyIGkgPSAxOyBpIDwgdGhpcy5tZWRpYS5sZW5ndGg7IGkrKykge1xuICAgICAgICB0aGlzLm1lZGlhW2ldID0gJ209JyArIHRoaXMubWVkaWFbaV07XG4gICAgICAgIGlmIChpICE9IHRoaXMubWVkaWEubGVuZ3RoIC0gMSkge1xuICAgICAgICAgICAgdGhpcy5tZWRpYVtpXSArPSAnXFxyXFxuJztcbiAgICAgICAgfVxuICAgIH1cbiAgICB0aGlzLnNlc3Npb24gPSB0aGlzLm1lZGlhLnNoaWZ0KCkgKyAnXFxyXFxuJztcbiAgICB0aGlzLnJhdyA9IHRoaXMuc2Vzc2lvbiArIHRoaXMubWVkaWEuam9pbignJyk7XG59XG4vKipcbiAqIFJldHVybnMgbWFwIG9mIE1lZGlhQ2hhbm5lbCBtYXBwZWQgcGVyIGNoYW5uZWwgaWR4LlxuICovXG5TRFAucHJvdG90eXBlLmdldE1lZGlhU3NyY01hcCA9IGZ1bmN0aW9uKCkge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB2YXIgbWVkaWFfc3NyY3MgPSB7fTtcbiAgICB2YXIgdG1wO1xuICAgIGZvciAodmFyIG1lZGlhaW5kZXggPSAwOyBtZWRpYWluZGV4IDwgc2VsZi5tZWRpYS5sZW5ndGg7IG1lZGlhaW5kZXgrKykge1xuICAgICAgICB0bXAgPSBTRFBVdGlsLmZpbmRfbGluZXMoc2VsZi5tZWRpYVttZWRpYWluZGV4XSwgJ2E9c3NyYzonKTtcbiAgICAgICAgdmFyIG1pZCA9IFNEUFV0aWwucGFyc2VfbWlkKFNEUFV0aWwuZmluZF9saW5lKHNlbGYubWVkaWFbbWVkaWFpbmRleF0sICdhPW1pZDonKSk7XG4gICAgICAgIHZhciBtZWRpYSA9IHtcbiAgICAgICAgICAgIG1lZGlhaW5kZXg6IG1lZGlhaW5kZXgsXG4gICAgICAgICAgICBtaWQ6IG1pZCxcbiAgICAgICAgICAgIHNzcmNzOiB7fSxcbiAgICAgICAgICAgIHNzcmNHcm91cHM6IFtdXG4gICAgICAgIH07XG4gICAgICAgIG1lZGlhX3NzcmNzW21lZGlhaW5kZXhdID0gbWVkaWE7XG4gICAgICAgIHRtcC5mb3JFYWNoKGZ1bmN0aW9uIChsaW5lKSB7XG4gICAgICAgICAgICB2YXIgbGluZXNzcmMgPSBsaW5lLnN1YnN0cmluZyg3KS5zcGxpdCgnICcpWzBdO1xuICAgICAgICAgICAgLy8gYWxsb2NhdGUgbmV3IENoYW5uZWxTc3JjXG4gICAgICAgICAgICBpZighbWVkaWEuc3NyY3NbbGluZXNzcmNdKSB7XG4gICAgICAgICAgICAgICAgbWVkaWEuc3NyY3NbbGluZXNzcmNdID0ge1xuICAgICAgICAgICAgICAgICAgICBzc3JjOiBsaW5lc3NyYyxcbiAgICAgICAgICAgICAgICAgICAgbGluZXM6IFtdXG4gICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIG1lZGlhLnNzcmNzW2xpbmVzc3JjXS5saW5lcy5wdXNoKGxpbmUpO1xuICAgICAgICB9KTtcbiAgICAgICAgdG1wID0gU0RQVXRpbC5maW5kX2xpbmVzKHNlbGYubWVkaWFbbWVkaWFpbmRleF0sICdhPXNzcmMtZ3JvdXA6Jyk7XG4gICAgICAgIHRtcC5mb3JFYWNoKGZ1bmN0aW9uKGxpbmUpe1xuICAgICAgICAgICAgdmFyIHNlbWFudGljcyA9IGxpbmUuc3Vic3RyKDAsIGlkeCkuc3Vic3RyKDEzKTtcbiAgICAgICAgICAgIHZhciBzc3JjcyA9IGxpbmUuc3Vic3RyKDE0ICsgc2VtYW50aWNzLmxlbmd0aCkuc3BsaXQoJyAnKTtcbiAgICAgICAgICAgIGlmIChzc3Jjcy5sZW5ndGggIT0gMCkge1xuICAgICAgICAgICAgICAgIG1lZGlhLnNzcmNHcm91cHMucHVzaCh7XG4gICAgICAgICAgICAgICAgICAgIHNlbWFudGljczogc2VtYW50aWNzLFxuICAgICAgICAgICAgICAgICAgICBzc3Jjczogc3NyY3NcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiBtZWRpYV9zc3Jjcztcbn07XG4vKipcbiAqIFJldHVybnMgPHR0PnRydWU8L3R0PiBpZiB0aGlzIFNEUCBjb250YWlucyBnaXZlbiBTU1JDLlxuICogQHBhcmFtIHNzcmMgdGhlIHNzcmMgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gPHR0PnRydWU8L3R0PiBpZiB0aGlzIFNEUCBjb250YWlucyBnaXZlbiBTU1JDLlxuICovXG5TRFAucHJvdG90eXBlLmNvbnRhaW5zU1NSQyA9IGZ1bmN0aW9uKHNzcmMpIHtcbiAgICB2YXIgbWVkaWFzID0gdGhpcy5nZXRNZWRpYVNzcmNNYXAoKTtcbiAgICB2YXIgY29udGFpbnMgPSBmYWxzZTtcbiAgICBPYmplY3Qua2V5cyhtZWRpYXMpLmZvckVhY2goZnVuY3Rpb24obWVkaWFpbmRleCl7XG4gICAgICAgIHZhciBtZWRpYSA9IG1lZGlhc1ttZWRpYWluZGV4XTtcbiAgICAgICAgLy9jb25zb2xlLmxvZyhcIkNoZWNrXCIsIGNoYW5uZWwsIHNzcmMpO1xuICAgICAgICBpZihPYmplY3Qua2V5cyhtZWRpYS5zc3JjcykuaW5kZXhPZihzc3JjKSAhPSAtMSl7XG4gICAgICAgICAgICBjb250YWlucyA9IHRydWU7XG4gICAgICAgIH1cbiAgICB9KTtcbiAgICByZXR1cm4gY29udGFpbnM7XG59O1xuXG5cbi8vIHJlbW92ZSBpU0FDIGFuZCBDTiBmcm9tIFNEUFxuU0RQLnByb3RvdHlwZS5tYW5nbGUgPSBmdW5jdGlvbiAoKSB7XG4gICAgdmFyIGksIGosIG1saW5lLCBsaW5lcywgcnRwbWFwLCBuZXdkZXNjO1xuICAgIGZvciAoaSA9IDA7IGkgPCB0aGlzLm1lZGlhLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGxpbmVzID0gdGhpcy5tZWRpYVtpXS5zcGxpdCgnXFxyXFxuJyk7XG4gICAgICAgIGxpbmVzLnBvcCgpOyAvLyByZW1vdmUgZW1wdHkgbGFzdCBlbGVtZW50XG4gICAgICAgIG1saW5lID0gU0RQVXRpbC5wYXJzZV9tbGluZShsaW5lcy5zaGlmdCgpKTtcbiAgICAgICAgaWYgKG1saW5lLm1lZGlhICE9ICdhdWRpbycpXG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgbmV3ZGVzYyA9ICcnO1xuICAgICAgICBtbGluZS5mbXQubGVuZ3RoID0gMDtcbiAgICAgICAgZm9yIChqID0gMDsgaiA8IGxpbmVzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICBpZiAobGluZXNbal0uc3Vic3RyKDAsIDkpID09ICdhPXJ0cG1hcDonKSB7XG4gICAgICAgICAgICAgICAgcnRwbWFwID0gU0RQVXRpbC5wYXJzZV9ydHBtYXAobGluZXNbal0pO1xuICAgICAgICAgICAgICAgIGlmIChydHBtYXAubmFtZSA9PSAnQ04nIHx8IHJ0cG1hcC5uYW1lID09ICdJU0FDJylcbiAgICAgICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICAgICAgbWxpbmUuZm10LnB1c2gocnRwbWFwLmlkKTtcbiAgICAgICAgICAgICAgICBuZXdkZXNjICs9IGxpbmVzW2pdICsgJ1xcclxcbic7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIG5ld2Rlc2MgKz0gbGluZXNbal0gKyAnXFxyXFxuJztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICB0aGlzLm1lZGlhW2ldID0gU0RQVXRpbC5idWlsZF9tbGluZShtbGluZSkgKyAnXFxyXFxuJztcbiAgICAgICAgdGhpcy5tZWRpYVtpXSArPSBuZXdkZXNjO1xuICAgIH1cbiAgICB0aGlzLnJhdyA9IHRoaXMuc2Vzc2lvbiArIHRoaXMubWVkaWEuam9pbignJyk7XG59O1xuXG4vLyByZW1vdmUgbGluZXMgbWF0Y2hpbmcgcHJlZml4IGZyb20gc2Vzc2lvbiBzZWN0aW9uXG5TRFAucHJvdG90eXBlLnJlbW92ZVNlc3Npb25MaW5lcyA9IGZ1bmN0aW9uKHByZWZpeCkge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB2YXIgbGluZXMgPSBTRFBVdGlsLmZpbmRfbGluZXModGhpcy5zZXNzaW9uLCBwcmVmaXgpO1xuICAgIGxpbmVzLmZvckVhY2goZnVuY3Rpb24obGluZSkge1xuICAgICAgICBzZWxmLnNlc3Npb24gPSBzZWxmLnNlc3Npb24ucmVwbGFjZShsaW5lICsgJ1xcclxcbicsICcnKTtcbiAgICB9KTtcbiAgICB0aGlzLnJhdyA9IHRoaXMuc2Vzc2lvbiArIHRoaXMubWVkaWEuam9pbignJyk7XG4gICAgcmV0dXJuIGxpbmVzO1xufVxuLy8gcmVtb3ZlIGxpbmVzIG1hdGNoaW5nIHByZWZpeCBmcm9tIGEgbWVkaWEgc2VjdGlvbiBzcGVjaWZpZWQgYnkgbWVkaWFpbmRleFxuLy8gVE9ETzogbm9uLW51bWVyaWMgbWVkaWFpbmRleCBjb3VsZCBtYXRjaCBtaWRcblNEUC5wcm90b3R5cGUucmVtb3ZlTWVkaWFMaW5lcyA9IGZ1bmN0aW9uKG1lZGlhaW5kZXgsIHByZWZpeCkge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB2YXIgbGluZXMgPSBTRFBVdGlsLmZpbmRfbGluZXModGhpcy5tZWRpYVttZWRpYWluZGV4XSwgcHJlZml4KTtcbiAgICBsaW5lcy5mb3JFYWNoKGZ1bmN0aW9uKGxpbmUpIHtcbiAgICAgICAgc2VsZi5tZWRpYVttZWRpYWluZGV4XSA9IHNlbGYubWVkaWFbbWVkaWFpbmRleF0ucmVwbGFjZShsaW5lICsgJ1xcclxcbicsICcnKTtcbiAgICB9KTtcbiAgICB0aGlzLnJhdyA9IHRoaXMuc2Vzc2lvbiArIHRoaXMubWVkaWEuam9pbignJyk7XG4gICAgcmV0dXJuIGxpbmVzO1xufVxuXG4vLyBhZGQgY29udGVudCdzIHRvIGEgamluZ2xlIGVsZW1lbnRcblNEUC5wcm90b3R5cGUudG9KaW5nbGUgPSBmdW5jdGlvbiAoZWxlbSwgdGhlY3JlYXRvciwgc3NyY3MpIHtcbi8vICAgIGNvbnNvbGUubG9nKFwiU1NSQ1wiICsgc3NyY3NbXCJhdWRpb1wiXSArIFwiIC0gXCIgKyBzc3Jjc1tcInZpZGVvXCJdKTtcbiAgICB2YXIgaSwgaiwgaywgbWxpbmUsIHNzcmMsIHJ0cG1hcCwgdG1wLCBsaW5lLCBsaW5lcztcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgLy8gbmV3IGJ1bmRsZSBwbGFuXG4gICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKHRoaXMuc2Vzc2lvbiwgJ2E9Z3JvdXA6JykpIHtcbiAgICAgICAgbGluZXMgPSBTRFBVdGlsLmZpbmRfbGluZXModGhpcy5zZXNzaW9uLCAnYT1ncm91cDonKTtcbiAgICAgICAgZm9yIChpID0gMDsgaSA8IGxpbmVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICB0bXAgPSBsaW5lc1tpXS5zcGxpdCgnICcpO1xuICAgICAgICAgICAgdmFyIHNlbWFudGljcyA9IHRtcC5zaGlmdCgpLnN1YnN0cig4KTtcbiAgICAgICAgICAgIGVsZW0uYygnZ3JvdXAnLCB7eG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6YXBwczpncm91cGluZzowJywgc2VtYW50aWNzOnNlbWFudGljc30pO1xuICAgICAgICAgICAgZm9yIChqID0gMDsgaiA8IHRtcC5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgICAgIGVsZW0uYygnY29udGVudCcsIHtuYW1lOiB0bXBbal19KS51cCgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICB9XG4gICAgfVxuICAgIGZvciAoaSA9IDA7IGkgPCB0aGlzLm1lZGlhLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIG1saW5lID0gU0RQVXRpbC5wYXJzZV9tbGluZSh0aGlzLm1lZGlhW2ldLnNwbGl0KCdcXHJcXG4nKVswXSk7XG4gICAgICAgIGlmICghKG1saW5lLm1lZGlhID09PSAnYXVkaW8nIHx8XG4gICAgICAgICAgICAgIG1saW5lLm1lZGlhID09PSAndmlkZW8nIHx8XG4gICAgICAgICAgICAgIG1saW5lLm1lZGlhID09PSAnYXBwbGljYXRpb24nKSlcbiAgICAgICAge1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKHRoaXMubWVkaWFbaV0sICdhPXNzcmM6JykpIHtcbiAgICAgICAgICAgIHNzcmMgPSBTRFBVdGlsLmZpbmRfbGluZSh0aGlzLm1lZGlhW2ldLCAnYT1zc3JjOicpLnN1YnN0cmluZyg3KS5zcGxpdCgnICcpWzBdOyAvLyB0YWtlIHRoZSBmaXJzdFxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgaWYoc3NyY3MgJiYgc3NyY3NbbWxpbmUubWVkaWFdKVxuICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgIHNzcmMgPSBzc3Jjc1ttbGluZS5tZWRpYV07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgc3NyYyA9IGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgZWxlbS5jKCdjb250ZW50Jywge2NyZWF0b3I6IHRoZWNyZWF0b3IsIG5hbWU6IG1saW5lLm1lZGlhfSk7XG4gICAgICAgIGlmIChTRFBVdGlsLmZpbmRfbGluZSh0aGlzLm1lZGlhW2ldLCAnYT1taWQ6JykpIHtcbiAgICAgICAgICAgIC8vIHByZWZlciBpZGVudGlmaWVyIGZyb20gYT1taWQgaWYgcHJlc2VudFxuICAgICAgICAgICAgdmFyIG1pZCA9IFNEUFV0aWwucGFyc2VfbWlkKFNEUFV0aWwuZmluZF9saW5lKHRoaXMubWVkaWFbaV0sICdhPW1pZDonKSk7XG4gICAgICAgICAgICBlbGVtLmF0dHJzKHsgbmFtZTogbWlkIH0pO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKHRoaXMubWVkaWFbaV0sICdhPXJ0cG1hcDonKS5sZW5ndGgpXG4gICAgICAgIHtcbiAgICAgICAgICAgIGVsZW0uYygnZGVzY3JpcHRpb24nLFxuICAgICAgICAgICAgICAgIHt4bWxuczogJ3Vybjp4bXBwOmppbmdsZTphcHBzOnJ0cDoxJyxcbiAgICAgICAgICAgICAgICAgICAgbWVkaWE6IG1saW5lLm1lZGlhIH0pO1xuICAgICAgICAgICAgaWYgKHNzcmMpIHtcbiAgICAgICAgICAgICAgICBlbGVtLmF0dHJzKHtzc3JjOiBzc3JjfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBmb3IgKGogPSAwOyBqIDwgbWxpbmUuZm10Lmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICAgICAgcnRwbWFwID0gU0RQVXRpbC5maW5kX2xpbmUodGhpcy5tZWRpYVtpXSwgJ2E9cnRwbWFwOicgKyBtbGluZS5mbXRbal0pO1xuICAgICAgICAgICAgICAgIGVsZW0uYygncGF5bG9hZC10eXBlJywgU0RQVXRpbC5wYXJzZV9ydHBtYXAocnRwbWFwKSk7XG4gICAgICAgICAgICAgICAgLy8gcHV0IGFueSAnYT1mbXRwOicgKyBtbGluZS5mbXRbal0gbGluZXMgaW50byA8cGFyYW0gbmFtZT1mb28gdmFsdWU9YmFyLz5cbiAgICAgICAgICAgICAgICBpZiAoU0RQVXRpbC5maW5kX2xpbmUodGhpcy5tZWRpYVtpXSwgJ2E9Zm10cDonICsgbWxpbmUuZm10W2pdKSkge1xuICAgICAgICAgICAgICAgICAgICB0bXAgPSBTRFBVdGlsLnBhcnNlX2ZtdHAoU0RQVXRpbC5maW5kX2xpbmUodGhpcy5tZWRpYVtpXSwgJ2E9Zm10cDonICsgbWxpbmUuZm10W2pdKSk7XG4gICAgICAgICAgICAgICAgICAgIGZvciAoayA9IDA7IGsgPCB0bXAubGVuZ3RoOyBrKyspIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsZW0uYygncGFyYW1ldGVyJywgdG1wW2tdKS51cCgpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIHRoaXMuUnRjcEZiVG9KaW5nbGUoaSwgZWxlbSwgbWxpbmUuZm10W2pdKTsgLy8gWEVQLTAyOTMgLS0gbWFwIGE9cnRjcC1mYlxuXG4gICAgICAgICAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKHRoaXMubWVkaWFbaV0sICdhPWNyeXB0bzonLCB0aGlzLnNlc3Npb24pKSB7XG4gICAgICAgICAgICAgICAgZWxlbS5jKCdlbmNyeXB0aW9uJywge3JlcXVpcmVkOiAxfSk7XG4gICAgICAgICAgICAgICAgdmFyIGNyeXB0byA9IFNEUFV0aWwuZmluZF9saW5lcyh0aGlzLm1lZGlhW2ldLCAnYT1jcnlwdG86JywgdGhpcy5zZXNzaW9uKTtcbiAgICAgICAgICAgICAgICBjcnlwdG8uZm9yRWFjaChmdW5jdGlvbihsaW5lKSB7XG4gICAgICAgICAgICAgICAgICAgIGVsZW0uYygnY3J5cHRvJywgU0RQVXRpbC5wYXJzZV9jcnlwdG8obGluZSkpLnVwKCk7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgZWxlbS51cCgpOyAvLyBlbmQgb2YgZW5jcnlwdGlvblxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoc3NyYykge1xuICAgICAgICAgICAgICAgIC8vIG5ldyBzdHlsZSBtYXBwaW5nXG4gICAgICAgICAgICAgICAgZWxlbS5jKCdzb3VyY2UnLCB7IHNzcmM6IHNzcmMsIHhtbG5zOiAndXJuOnhtcHA6amluZ2xlOmFwcHM6cnRwOnNzbWE6MCcgfSk7XG4gICAgICAgICAgICAgICAgLy8gRklYTUU6IGdyb3VwIGJ5IHNzcmMgYW5kIHN1cHBvcnQgbXVsdGlwbGUgZGlmZmVyZW50IHNzcmNzXG4gICAgICAgICAgICAgICAgdmFyIHNzcmNsaW5lcyA9IFNEUFV0aWwuZmluZF9saW5lcyh0aGlzLm1lZGlhW2ldLCAnYT1zc3JjOicpO1xuICAgICAgICAgICAgICAgIGlmKHNzcmNsaW5lcy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgICAgIHNzcmNsaW5lcy5mb3JFYWNoKGZ1bmN0aW9uIChsaW5lKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZHggPSBsaW5lLmluZGV4T2YoJyAnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBsaW5lc3NyYyA9IGxpbmUuc3Vic3RyKDAsIGlkeCkuc3Vic3RyKDcpO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGxpbmVzc3JjICE9IHNzcmMpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbGVtLnVwKCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgc3NyYyA9IGxpbmVzc3JjO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsZW0uYygnc291cmNlJywgeyBzc3JjOiBzc3JjLCB4bWxuczogJ3Vybjp4bXBwOmppbmdsZTphcHBzOnJ0cDpzc21hOjAnIH0pO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGt2ID0gbGluZS5zdWJzdHIoaWR4ICsgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICBlbGVtLmMoJ3BhcmFtZXRlcicpO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGt2LmluZGV4T2YoJzonKSA9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsZW0uYXR0cnMoeyBuYW1lOiBrdiB9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS5hdHRycyh7IG5hbWU6IGt2LnNwbGl0KCc6JywgMilbMF0gfSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS5hdHRycyh7IHZhbHVlOiBrdi5zcGxpdCgnOicsIDIpWzFdIH0pO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICBlbGVtLnVwKCk7XG4gICAgICAgICAgICAgICAgICAgIGVsZW0uYygnc291cmNlJywgeyBzc3JjOiBzc3JjLCB4bWxuczogJ3Vybjp4bXBwOmppbmdsZTphcHBzOnJ0cDpzc21hOjAnIH0pO1xuICAgICAgICAgICAgICAgICAgICBlbGVtLmMoJ3BhcmFtZXRlcicpO1xuICAgICAgICAgICAgICAgICAgICBlbGVtLmF0dHJzKHtuYW1lOiBcImNuYW1lXCIsIHZhbHVlOk1hdGgucmFuZG9tKCkudG9TdHJpbmcoMzYpLnN1YnN0cmluZyg3KX0pO1xuICAgICAgICAgICAgICAgICAgICBlbGVtLnVwKCk7XG4gICAgICAgICAgICAgICAgICAgIHZhciBtc2lkID0gbnVsbDtcbiAgICAgICAgICAgICAgICAgICAgaWYobWxpbmUubWVkaWEgPT0gXCJhdWRpb1wiKVxuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICBtc2lkID0gUlRDLmxvY2FsQXVkaW8uZ2V0SWQoKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG1zaWQgPSBSVEMubG9jYWxWaWRlby5nZXRJZCgpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGlmKG1zaWQgIT0gbnVsbClcbiAgICAgICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICAgICAgbXNpZCA9IG1zaWQucmVwbGFjZSgvW1xceyxcXH1dL2csXCJcIik7XG4gICAgICAgICAgICAgICAgICAgICAgICBlbGVtLmMoJ3BhcmFtZXRlcicpO1xuICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS5hdHRycyh7bmFtZTogXCJtc2lkXCIsIHZhbHVlOm1zaWR9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsZW0udXAoKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsZW0uYygncGFyYW1ldGVyJyk7XG4gICAgICAgICAgICAgICAgICAgICAgICBlbGVtLmF0dHJzKHtuYW1lOiBcIm1zbGFiZWxcIiwgdmFsdWU6bXNpZH0pO1xuICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS5jKCdwYXJhbWV0ZXInKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGVsZW0uYXR0cnMoe25hbWU6IFwibGFiZWxcIiwgdmFsdWU6bXNpZH0pO1xuICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICAgICAgICAgICAgICB9XG5cblxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIC8vIFhFUC0wMzM5IGhhbmRsZSBzc3JjLWdyb3VwIGF0dHJpYnV0ZXNcbiAgICAgICAgICAgICAgICB2YXIgc3NyY19ncm91cF9saW5lcyA9IFNEUFV0aWwuZmluZF9saW5lcyh0aGlzLm1lZGlhW2ldLCAnYT1zc3JjLWdyb3VwOicpO1xuICAgICAgICAgICAgICAgIHNzcmNfZ3JvdXBfbGluZXMuZm9yRWFjaChmdW5jdGlvbihsaW5lKSB7XG4gICAgICAgICAgICAgICAgICAgIGlkeCA9IGxpbmUuaW5kZXhPZignICcpO1xuICAgICAgICAgICAgICAgICAgICB2YXIgc2VtYW50aWNzID0gbGluZS5zdWJzdHIoMCwgaWR4KS5zdWJzdHIoMTMpO1xuICAgICAgICAgICAgICAgICAgICB2YXIgc3NyY3MgPSBsaW5lLnN1YnN0cigxNCArIHNlbWFudGljcy5sZW5ndGgpLnNwbGl0KCcgJyk7XG4gICAgICAgICAgICAgICAgICAgIGlmIChzc3Jjcy5sZW5ndGggIT0gMCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS5jKCdzc3JjLWdyb3VwJywgeyBzZW1hbnRpY3M6IHNlbWFudGljcywgeG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6c3NtYTowJyB9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIHNzcmNzLmZvckVhY2goZnVuY3Rpb24oc3NyYykge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsZW0uYygnc291cmNlJywgeyBzc3JjOiBzc3JjIH0pXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC51cCgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgICAgICAgICBlbGVtLnVwKCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKHRoaXMubWVkaWFbaV0sICdhPXJ0Y3AtbXV4JykpIHtcbiAgICAgICAgICAgICAgICBlbGVtLmMoJ3J0Y3AtbXV4JykudXAoKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gWEVQLTAyOTMgLS0gbWFwIGE9cnRjcC1mYjoqXG4gICAgICAgICAgICB0aGlzLlJ0Y3BGYlRvSmluZ2xlKGksIGVsZW0sICcqJyk7XG5cbiAgICAgICAgICAgIC8vIFhFUC0wMjk0XG4gICAgICAgICAgICBpZiAoU0RQVXRpbC5maW5kX2xpbmUodGhpcy5tZWRpYVtpXSwgJ2E9ZXh0bWFwOicpKSB7XG4gICAgICAgICAgICAgICAgbGluZXMgPSBTRFBVdGlsLmZpbmRfbGluZXModGhpcy5tZWRpYVtpXSwgJ2E9ZXh0bWFwOicpO1xuICAgICAgICAgICAgICAgIGZvciAoaiA9IDA7IGogPCBsaW5lcy5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgICAgICAgICB0bXAgPSBTRFBVdGlsLnBhcnNlX2V4dG1hcChsaW5lc1tqXSk7XG4gICAgICAgICAgICAgICAgICAgIGVsZW0uYygncnRwLWhkcmV4dCcsIHsgeG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6cnRwLWhkcmV4dDowJyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHVyaTogdG1wLnVyaSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGlkOiB0bXAudmFsdWUgfSk7XG4gICAgICAgICAgICAgICAgICAgIGlmICh0bXAuaGFzT3duUHJvcGVydHkoJ2RpcmVjdGlvbicpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzd2l0Y2ggKHRtcC5kaXJlY3Rpb24pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYXNlICdzZW5kb25seSc6XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsZW0uYXR0cnMoe3NlbmRlcnM6ICdyZXNwb25kZXInfSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhc2UgJ3JlY3Zvbmx5JzpcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbS5hdHRycyh7c2VuZGVyczogJ2luaXRpYXRvcid9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FzZSAnc2VuZHJlY3YnOlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbGVtLmF0dHJzKHtzZW5kZXJzOiAnYm90aCd9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FzZSAnaW5hY3RpdmUnOlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbGVtLmF0dHJzKHtzZW5kZXJzOiAnbm9uZSd9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgLy8gVE9ETzogaGFuZGxlIHBhcmFtc1xuICAgICAgICAgICAgICAgICAgICBlbGVtLnVwKCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxlbS51cCgpOyAvLyBlbmQgb2YgZGVzY3JpcHRpb25cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIG1hcCBpY2UtdWZyYWcvcHdkLCBkdGxzIGZpbmdlcnByaW50LCBjYW5kaWRhdGVzXG4gICAgICAgIHRoaXMuVHJhbnNwb3J0VG9KaW5nbGUoaSwgZWxlbSk7XG5cbiAgICAgICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKHRoaXMubWVkaWFbaV0sICdhPXNlbmRyZWN2JywgdGhpcy5zZXNzaW9uKSkge1xuICAgICAgICAgICAgZWxlbS5hdHRycyh7c2VuZGVyczogJ2JvdGgnfSk7XG4gICAgICAgIH0gZWxzZSBpZiAoU0RQVXRpbC5maW5kX2xpbmUodGhpcy5tZWRpYVtpXSwgJ2E9c2VuZG9ubHknLCB0aGlzLnNlc3Npb24pKSB7XG4gICAgICAgICAgICBlbGVtLmF0dHJzKHtzZW5kZXJzOiAnaW5pdGlhdG9yJ30pO1xuICAgICAgICB9IGVsc2UgaWYgKFNEUFV0aWwuZmluZF9saW5lKHRoaXMubWVkaWFbaV0sICdhPXJlY3Zvbmx5JywgdGhpcy5zZXNzaW9uKSkge1xuICAgICAgICAgICAgZWxlbS5hdHRycyh7c2VuZGVyczogJ3Jlc3BvbmRlcid9KTtcbiAgICAgICAgfSBlbHNlIGlmIChTRFBVdGlsLmZpbmRfbGluZSh0aGlzLm1lZGlhW2ldLCAnYT1pbmFjdGl2ZScsIHRoaXMuc2Vzc2lvbikpIHtcbiAgICAgICAgICAgIGVsZW0uYXR0cnMoe3NlbmRlcnM6ICdub25lJ30pO1xuICAgICAgICB9XG4gICAgICAgIGlmIChtbGluZS5wb3J0ID09ICcwJykge1xuICAgICAgICAgICAgLy8gZXN0b3MgaGFjayB0byByZWplY3QgYW4gbS1saW5lXG4gICAgICAgICAgICBlbGVtLmF0dHJzKHtzZW5kZXJzOiAncmVqZWN0ZWQnfSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxlbS51cCgpOyAvLyBlbmQgb2YgY29udGVudFxuICAgIH1cbiAgICBlbGVtLnVwKCk7XG4gICAgcmV0dXJuIGVsZW07XG59O1xuXG5TRFAucHJvdG90eXBlLlRyYW5zcG9ydFRvSmluZ2xlID0gZnVuY3Rpb24gKG1lZGlhaW5kZXgsIGVsZW0pIHtcbiAgICB2YXIgaSA9IG1lZGlhaW5kZXg7XG4gICAgdmFyIHRtcDtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgZWxlbS5jKCd0cmFuc3BvcnQnKTtcblxuICAgIC8vIFhFUC0wMzQzIERUTFMvU0NUUFxuICAgIGlmIChTRFBVdGlsLmZpbmRfbGluZSh0aGlzLm1lZGlhW21lZGlhaW5kZXhdLCAnYT1zY3RwbWFwOicpLmxlbmd0aClcbiAgICB7XG4gICAgICAgIHZhciBzY3RwbWFwID0gU0RQVXRpbC5maW5kX2xpbmUoXG4gICAgICAgICAgICB0aGlzLm1lZGlhW2ldLCAnYT1zY3RwbWFwOicsIHNlbGYuc2Vzc2lvbik7XG4gICAgICAgIGlmIChzY3RwbWFwKVxuICAgICAgICB7XG4gICAgICAgICAgICB2YXIgc2N0cEF0dHJzID0gU0RQVXRpbC5wYXJzZV9zY3RwbWFwKHNjdHBtYXApO1xuICAgICAgICAgICAgZWxlbS5jKCdzY3RwbWFwJyxcbiAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgIHhtbG5zOiAndXJuOnhtcHA6amluZ2xlOnRyYW5zcG9ydHM6ZHRscy1zY3RwOjEnLFxuICAgICAgICAgICAgICAgICAgICBudW1iZXI6IHNjdHBBdHRyc1swXSwgLyogU0NUUCBwb3J0ICovXG4gICAgICAgICAgICAgICAgICAgIHByb3RvY29sOiBzY3RwQXR0cnNbMV0sIC8qIHByb3RvY29sICovXG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAvLyBPcHRpb25hbCBzdHJlYW0gY291bnQgYXR0cmlidXRlXG4gICAgICAgICAgICBpZiAoc2N0cEF0dHJzLmxlbmd0aCA+IDIpXG4gICAgICAgICAgICAgICAgZWxlbS5hdHRycyh7IHN0cmVhbXM6IHNjdHBBdHRyc1syXX0pO1xuICAgICAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8vIFhFUC0wMzIwXG4gICAgdmFyIGZpbmdlcnByaW50cyA9IFNEUFV0aWwuZmluZF9saW5lcyh0aGlzLm1lZGlhW21lZGlhaW5kZXhdLCAnYT1maW5nZXJwcmludDonLCB0aGlzLnNlc3Npb24pO1xuICAgIGZpbmdlcnByaW50cy5mb3JFYWNoKGZ1bmN0aW9uKGxpbmUpIHtcbiAgICAgICAgdG1wID0gU0RQVXRpbC5wYXJzZV9maW5nZXJwcmludChsaW5lKTtcbiAgICAgICAgdG1wLnhtbG5zID0gJ3Vybjp4bXBwOmppbmdsZTphcHBzOmR0bHM6MCc7XG4gICAgICAgIGVsZW0uYygnZmluZ2VycHJpbnQnKS50KHRtcC5maW5nZXJwcmludCk7XG4gICAgICAgIGRlbGV0ZSB0bXAuZmluZ2VycHJpbnQ7XG4gICAgICAgIGxpbmUgPSBTRFBVdGlsLmZpbmRfbGluZShzZWxmLm1lZGlhW21lZGlhaW5kZXhdLCAnYT1zZXR1cDonLCBzZWxmLnNlc3Npb24pO1xuICAgICAgICBpZiAobGluZSkge1xuICAgICAgICAgICAgdG1wLnNldHVwID0gbGluZS5zdWJzdHIoOCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxlbS5hdHRycyh0bXApO1xuICAgICAgICBlbGVtLnVwKCk7IC8vIGVuZCBvZiBmaW5nZXJwcmludFxuICAgIH0pO1xuICAgIHRtcCA9IFNEUFV0aWwuaWNlcGFyYW1zKHRoaXMubWVkaWFbbWVkaWFpbmRleF0sIHRoaXMuc2Vzc2lvbik7XG4gICAgaWYgKHRtcCkge1xuICAgICAgICB0bXAueG1sbnMgPSAndXJuOnhtcHA6amluZ2xlOnRyYW5zcG9ydHM6aWNlLXVkcDoxJztcbiAgICAgICAgZWxlbS5hdHRycyh0bXApO1xuICAgICAgICAvLyBYRVAtMDE3NlxuICAgICAgICBpZiAoU0RQVXRpbC5maW5kX2xpbmUodGhpcy5tZWRpYVttZWRpYWluZGV4XSwgJ2E9Y2FuZGlkYXRlOicsIHRoaXMuc2Vzc2lvbikpIHsgLy8gYWRkIGFueSBhPWNhbmRpZGF0ZSBsaW5lc1xuICAgICAgICAgICAgdmFyIGxpbmVzID0gU0RQVXRpbC5maW5kX2xpbmVzKHRoaXMubWVkaWFbbWVkaWFpbmRleF0sICdhPWNhbmRpZGF0ZTonLCB0aGlzLnNlc3Npb24pO1xuICAgICAgICAgICAgbGluZXMuZm9yRWFjaChmdW5jdGlvbiAobGluZSkge1xuICAgICAgICAgICAgICAgIGVsZW0uYygnY2FuZGlkYXRlJywgU0RQVXRpbC5jYW5kaWRhdGVUb0ppbmdsZShsaW5lKSkudXAoKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgfVxuICAgIGVsZW0udXAoKTsgLy8gZW5kIG9mIHRyYW5zcG9ydFxufVxuXG5TRFAucHJvdG90eXBlLlJ0Y3BGYlRvSmluZ2xlID0gZnVuY3Rpb24gKG1lZGlhaW5kZXgsIGVsZW0sIHBheWxvYWR0eXBlKSB7IC8vIFhFUC0wMjkzXG4gICAgdmFyIGxpbmVzID0gU0RQVXRpbC5maW5kX2xpbmVzKHRoaXMubWVkaWFbbWVkaWFpbmRleF0sICdhPXJ0Y3AtZmI6JyArIHBheWxvYWR0eXBlKTtcbiAgICBsaW5lcy5mb3JFYWNoKGZ1bmN0aW9uIChsaW5lKSB7XG4gICAgICAgIHZhciB0bXAgPSBTRFBVdGlsLnBhcnNlX3J0Y3BmYihsaW5lKTtcbiAgICAgICAgaWYgKHRtcC50eXBlID09ICd0cnItaW50Jykge1xuICAgICAgICAgICAgZWxlbS5jKCdydGNwLWZiLXRyci1pbnQnLCB7eG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6cnRjcC1mYjowJywgdmFsdWU6IHRtcC5wYXJhbXNbMF19KTtcbiAgICAgICAgICAgIGVsZW0udXAoKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGVsZW0uYygncnRjcC1mYicsIHt4bWxuczogJ3Vybjp4bXBwOmppbmdsZTphcHBzOnJ0cDpydGNwLWZiOjAnLCB0eXBlOiB0bXAudHlwZX0pO1xuICAgICAgICAgICAgaWYgKHRtcC5wYXJhbXMubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgICAgIGVsZW0uYXR0cnMoeydzdWJ0eXBlJzogdG1wLnBhcmFtc1swXX0pO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICB9XG4gICAgfSk7XG59O1xuXG5TRFAucHJvdG90eXBlLlJ0Y3BGYkZyb21KaW5nbGUgPSBmdW5jdGlvbiAoZWxlbSwgcGF5bG9hZHR5cGUpIHsgLy8gWEVQLTAyOTNcbiAgICB2YXIgbWVkaWEgPSAnJztcbiAgICB2YXIgdG1wID0gZWxlbS5maW5kKCc+cnRjcC1mYi10cnItaW50W3htbG5zPVwidXJuOnhtcHA6amluZ2xlOmFwcHM6cnRwOnJ0Y3AtZmI6MFwiXScpO1xuICAgIGlmICh0bXAubGVuZ3RoKSB7XG4gICAgICAgIG1lZGlhICs9ICdhPXJ0Y3AtZmI6JyArICcqJyArICcgJyArICd0cnItaW50JyArICcgJztcbiAgICAgICAgaWYgKHRtcC5hdHRyKCd2YWx1ZScpKSB7XG4gICAgICAgICAgICBtZWRpYSArPSB0bXAuYXR0cigndmFsdWUnKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIG1lZGlhICs9ICcwJztcbiAgICAgICAgfVxuICAgICAgICBtZWRpYSArPSAnXFxyXFxuJztcbiAgICB9XG4gICAgdG1wID0gZWxlbS5maW5kKCc+cnRjcC1mYlt4bWxucz1cInVybjp4bXBwOmppbmdsZTphcHBzOnJ0cDpydGNwLWZiOjBcIl0nKTtcbiAgICB0bXAuZWFjaChmdW5jdGlvbiAoKSB7XG4gICAgICAgIG1lZGlhICs9ICdhPXJ0Y3AtZmI6JyArIHBheWxvYWR0eXBlICsgJyAnICsgJCh0aGlzKS5hdHRyKCd0eXBlJyk7XG4gICAgICAgIGlmICgkKHRoaXMpLmF0dHIoJ3N1YnR5cGUnKSkge1xuICAgICAgICAgICAgbWVkaWEgKz0gJyAnICsgJCh0aGlzKS5hdHRyKCdzdWJ0eXBlJyk7XG4gICAgICAgIH1cbiAgICAgICAgbWVkaWEgKz0gJ1xcclxcbic7XG4gICAgfSk7XG4gICAgcmV0dXJuIG1lZGlhO1xufTtcblxuLy8gY29uc3RydWN0IGFuIFNEUCBmcm9tIGEgamluZ2xlIHN0YW56YVxuU0RQLnByb3RvdHlwZS5mcm9tSmluZ2xlID0gZnVuY3Rpb24gKGppbmdsZSkge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB0aGlzLnJhdyA9ICd2PTBcXHJcXG4nICtcbiAgICAgICAgJ289LSAnICsgJzE5MjM1MTg1MTYnICsgJyAyIElOIElQNCAwLjAuMC4wXFxyXFxuJyArLy8gRklYTUVcbiAgICAgICAgJ3M9LVxcclxcbicgK1xuICAgICAgICAndD0wIDBcXHJcXG4nO1xuICAgIC8vIGh0dHA6Ly90b29scy5pZXRmLm9yZy9odG1sL2RyYWZ0LWlldGYtbW11c2ljLXNkcC1idW5kbGUtbmVnb3RpYXRpb24tMDQjc2VjdGlvbi04XG4gICAgaWYgKCQoamluZ2xlKS5maW5kKCc+Z3JvdXBbeG1sbnM9XCJ1cm46eG1wcDpqaW5nbGU6YXBwczpncm91cGluZzowXCJdJykubGVuZ3RoKSB7XG4gICAgICAgICQoamluZ2xlKS5maW5kKCc+Z3JvdXBbeG1sbnM9XCJ1cm46eG1wcDpqaW5nbGU6YXBwczpncm91cGluZzowXCJdJykuZWFjaChmdW5jdGlvbiAoaWR4LCBncm91cCkge1xuICAgICAgICAgICAgdmFyIGNvbnRlbnRzID0gJChncm91cCkuZmluZCgnPmNvbnRlbnQnKS5tYXAoZnVuY3Rpb24gKGlkeCwgY29udGVudCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBjb250ZW50LmdldEF0dHJpYnV0ZSgnbmFtZScpO1xuICAgICAgICAgICAgfSkuZ2V0KCk7XG4gICAgICAgICAgICBpZiAoY29udGVudHMubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgICAgIHNlbGYucmF3ICs9ICdhPWdyb3VwOicgKyAoZ3JvdXAuZ2V0QXR0cmlidXRlKCdzZW1hbnRpY3MnKSB8fCBncm91cC5nZXRBdHRyaWJ1dGUoJ3R5cGUnKSkgKyAnICcgKyBjb250ZW50cy5qb2luKCcgJykgKyAnXFxyXFxuJztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgdGhpcy5zZXNzaW9uID0gdGhpcy5yYXc7XG4gICAgamluZ2xlLmZpbmQoJz5jb250ZW50JykuZWFjaChmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBtID0gc2VsZi5qaW5nbGUybWVkaWEoJCh0aGlzKSk7XG4gICAgICAgIHNlbGYubWVkaWEucHVzaChtKTtcbiAgICB9KTtcblxuICAgIC8vIHJlY29uc3RydWN0IG1zaWQtc2VtYW50aWMgLS0gYXBwYXJlbnRseSBub3QgbmVjZXNzYXJ5XG4gICAgLypcbiAgICAgdmFyIG1zaWQgPSBTRFBVdGlsLnBhcnNlX3NzcmModGhpcy5yYXcpO1xuICAgICBpZiAobXNpZC5oYXNPd25Qcm9wZXJ0eSgnbXNsYWJlbCcpKSB7XG4gICAgIHRoaXMuc2Vzc2lvbiArPSBcImE9bXNpZC1zZW1hbnRpYzogV01TIFwiICsgbXNpZC5tc2xhYmVsICsgXCJcXHJcXG5cIjtcbiAgICAgfVxuICAgICAqL1xuXG4gICAgdGhpcy5yYXcgPSB0aGlzLnNlc3Npb24gKyB0aGlzLm1lZGlhLmpvaW4oJycpO1xufTtcblxuLy8gdHJhbnNsYXRlIGEgamluZ2xlIGNvbnRlbnQgZWxlbWVudCBpbnRvIGFuIGFuIFNEUCBtZWRpYSBwYXJ0XG5TRFAucHJvdG90eXBlLmppbmdsZTJtZWRpYSA9IGZ1bmN0aW9uIChjb250ZW50KSB7XG4gICAgdmFyIG1lZGlhID0gJycsXG4gICAgICAgIGRlc2MgPSBjb250ZW50LmZpbmQoJ2Rlc2NyaXB0aW9uJyksXG4gICAgICAgIHNzcmMgPSBkZXNjLmF0dHIoJ3NzcmMnKSxcbiAgICAgICAgc2VsZiA9IHRoaXMsXG4gICAgICAgIHRtcDtcbiAgICB2YXIgc2N0cCA9IGNvbnRlbnQuZmluZChcbiAgICAgICAgJz50cmFuc3BvcnQ+c2N0cG1hcFt4bWxucz1cInVybjp4bXBwOmppbmdsZTp0cmFuc3BvcnRzOmR0bHMtc2N0cDoxXCJdJyk7XG5cbiAgICB0bXAgPSB7IG1lZGlhOiBkZXNjLmF0dHIoJ21lZGlhJykgfTtcbiAgICB0bXAucG9ydCA9ICcxJztcbiAgICBpZiAoY29udGVudC5hdHRyKCdzZW5kZXJzJykgPT0gJ3JlamVjdGVkJykge1xuICAgICAgICAvLyBlc3RvcyBoYWNrIHRvIHJlamVjdCBhbiBtLWxpbmUuXG4gICAgICAgIHRtcC5wb3J0ID0gJzAnO1xuICAgIH1cbiAgICBpZiAoY29udGVudC5maW5kKCc+dHJhbnNwb3J0PmZpbmdlcnByaW50JykubGVuZ3RoIHx8IGRlc2MuZmluZCgnZW5jcnlwdGlvbicpLmxlbmd0aCkge1xuICAgICAgICBpZiAoc2N0cC5sZW5ndGgpXG4gICAgICAgICAgICB0bXAucHJvdG8gPSAnRFRMUy9TQ1RQJztcbiAgICAgICAgZWxzZVxuICAgICAgICAgICAgdG1wLnByb3RvID0gJ1JUUC9TQVZQRic7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgdG1wLnByb3RvID0gJ1JUUC9BVlBGJztcbiAgICB9XG4gICAgaWYgKCFzY3RwLmxlbmd0aClcbiAgICB7XG4gICAgICAgIHRtcC5mbXQgPSBkZXNjLmZpbmQoJ3BheWxvYWQtdHlwZScpLm1hcChcbiAgICAgICAgICAgIGZ1bmN0aW9uICgpIHsgcmV0dXJuIHRoaXMuZ2V0QXR0cmlidXRlKCdpZCcpOyB9KS5nZXQoKTtcbiAgICAgICAgbWVkaWEgKz0gU0RQVXRpbC5idWlsZF9tbGluZSh0bXApICsgJ1xcclxcbic7XG4gICAgfVxuICAgIGVsc2VcbiAgICB7XG4gICAgICAgIG1lZGlhICs9ICdtPWFwcGxpY2F0aW9uIDEgRFRMUy9TQ1RQICcgKyBzY3RwLmF0dHIoJ251bWJlcicpICsgJ1xcclxcbic7XG4gICAgICAgIG1lZGlhICs9ICdhPXNjdHBtYXA6JyArIHNjdHAuYXR0cignbnVtYmVyJykgK1xuICAgICAgICAgICAgJyAnICsgc2N0cC5hdHRyKCdwcm90b2NvbCcpO1xuXG4gICAgICAgIHZhciBzdHJlYW1Db3VudCA9IHNjdHAuYXR0cignc3RyZWFtcycpO1xuICAgICAgICBpZiAoc3RyZWFtQ291bnQpXG4gICAgICAgICAgICBtZWRpYSArPSAnICcgKyBzdHJlYW1Db3VudCArICdcXHJcXG4nO1xuICAgICAgICBlbHNlXG4gICAgICAgICAgICBtZWRpYSArPSAnXFxyXFxuJztcbiAgICB9XG5cbiAgICBtZWRpYSArPSAnYz1JTiBJUDQgMC4wLjAuMFxcclxcbic7XG4gICAgaWYgKCFzY3RwLmxlbmd0aClcbiAgICAgICAgbWVkaWEgKz0gJ2E9cnRjcDoxIElOIElQNCAwLjAuMC4wXFxyXFxuJztcbiAgICB0bXAgPSBjb250ZW50LmZpbmQoJz50cmFuc3BvcnRbeG1sbnM9XCJ1cm46eG1wcDpqaW5nbGU6dHJhbnNwb3J0czppY2UtdWRwOjFcIl0nKTtcbiAgICBpZiAodG1wLmxlbmd0aCkge1xuICAgICAgICBpZiAodG1wLmF0dHIoJ3VmcmFnJykpIHtcbiAgICAgICAgICAgIG1lZGlhICs9IFNEUFV0aWwuYnVpbGRfaWNldWZyYWcodG1wLmF0dHIoJ3VmcmFnJykpICsgJ1xcclxcbic7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRtcC5hdHRyKCdwd2QnKSkge1xuICAgICAgICAgICAgbWVkaWEgKz0gU0RQVXRpbC5idWlsZF9pY2Vwd2QodG1wLmF0dHIoJ3B3ZCcpKSArICdcXHJcXG4nO1xuICAgICAgICB9XG4gICAgICAgIHRtcC5maW5kKCc+ZmluZ2VycHJpbnQnKS5lYWNoKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIC8vIEZJWE1FOiBjaGVjayBuYW1lc3BhY2UgYXQgc29tZSBwb2ludFxuICAgICAgICAgICAgbWVkaWEgKz0gJ2E9ZmluZ2VycHJpbnQ6JyArIHRoaXMuZ2V0QXR0cmlidXRlKCdoYXNoJyk7XG4gICAgICAgICAgICBtZWRpYSArPSAnICcgKyAkKHRoaXMpLnRleHQoKTtcbiAgICAgICAgICAgIG1lZGlhICs9ICdcXHJcXG4nO1xuICAgICAgICAgICAgaWYgKHRoaXMuZ2V0QXR0cmlidXRlKCdzZXR1cCcpKSB7XG4gICAgICAgICAgICAgICAgbWVkaWEgKz0gJ2E9c2V0dXA6JyArIHRoaXMuZ2V0QXR0cmlidXRlKCdzZXR1cCcpICsgJ1xcclxcbic7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICBzd2l0Y2ggKGNvbnRlbnQuYXR0cignc2VuZGVycycpKSB7XG4gICAgICAgIGNhc2UgJ2luaXRpYXRvcic6XG4gICAgICAgICAgICBtZWRpYSArPSAnYT1zZW5kb25seVxcclxcbic7XG4gICAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAncmVzcG9uZGVyJzpcbiAgICAgICAgICAgIG1lZGlhICs9ICdhPXJlY3Zvbmx5XFxyXFxuJztcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlICdub25lJzpcbiAgICAgICAgICAgIG1lZGlhICs9ICdhPWluYWN0aXZlXFxyXFxuJztcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlICdib3RoJzpcbiAgICAgICAgICAgIG1lZGlhICs9ICdhPXNlbmRyZWN2XFxyXFxuJztcbiAgICAgICAgICAgIGJyZWFrO1xuICAgIH1cbiAgICBtZWRpYSArPSAnYT1taWQ6JyArIGNvbnRlbnQuYXR0cignbmFtZScpICsgJ1xcclxcbic7XG5cbiAgICAvLyA8ZGVzY3JpcHRpb24+PHJ0Y3AtbXV4Lz48L2Rlc2NyaXB0aW9uPlxuICAgIC8vIHNlZSBodHRwOi8vY29kZS5nb29nbGUuY29tL3AvbGliamluZ2xlL2lzc3Vlcy9kZXRhaWw/aWQ9MzA5IC0tIG5vIHNwZWMgdGhvdWdoXG4gICAgLy8gYW5kIGh0dHA6Ly9tYWlsLmphYmJlci5vcmcvcGlwZXJtYWlsL2ppbmdsZS8yMDExLURlY2VtYmVyLzAwMTc2MS5odG1sXG4gICAgaWYgKGRlc2MuZmluZCgncnRjcC1tdXgnKS5sZW5ndGgpIHtcbiAgICAgICAgbWVkaWEgKz0gJ2E9cnRjcC1tdXhcXHJcXG4nO1xuICAgIH1cblxuICAgIGlmIChkZXNjLmZpbmQoJ2VuY3J5cHRpb24nKS5sZW5ndGgpIHtcbiAgICAgICAgZGVzYy5maW5kKCdlbmNyeXB0aW9uPmNyeXB0bycpLmVhY2goZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgbWVkaWEgKz0gJ2E9Y3J5cHRvOicgKyB0aGlzLmdldEF0dHJpYnV0ZSgndGFnJyk7XG4gICAgICAgICAgICBtZWRpYSArPSAnICcgKyB0aGlzLmdldEF0dHJpYnV0ZSgnY3J5cHRvLXN1aXRlJyk7XG4gICAgICAgICAgICBtZWRpYSArPSAnICcgKyB0aGlzLmdldEF0dHJpYnV0ZSgna2V5LXBhcmFtcycpO1xuICAgICAgICAgICAgaWYgKHRoaXMuZ2V0QXR0cmlidXRlKCdzZXNzaW9uLXBhcmFtcycpKSB7XG4gICAgICAgICAgICAgICAgbWVkaWEgKz0gJyAnICsgdGhpcy5nZXRBdHRyaWJ1dGUoJ3Nlc3Npb24tcGFyYW1zJyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBtZWRpYSArPSAnXFxyXFxuJztcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIGRlc2MuZmluZCgncGF5bG9hZC10eXBlJykuZWFjaChmdW5jdGlvbiAoKSB7XG4gICAgICAgIG1lZGlhICs9IFNEUFV0aWwuYnVpbGRfcnRwbWFwKHRoaXMpICsgJ1xcclxcbic7XG4gICAgICAgIGlmICgkKHRoaXMpLmZpbmQoJz5wYXJhbWV0ZXInKS5sZW5ndGgpIHtcbiAgICAgICAgICAgIG1lZGlhICs9ICdhPWZtdHA6JyArIHRoaXMuZ2V0QXR0cmlidXRlKCdpZCcpICsgJyAnO1xuICAgICAgICAgICAgbWVkaWEgKz0gJCh0aGlzKS5maW5kKCdwYXJhbWV0ZXInKS5tYXAoZnVuY3Rpb24gKCkgeyByZXR1cm4gKHRoaXMuZ2V0QXR0cmlidXRlKCduYW1lJykgPyAodGhpcy5nZXRBdHRyaWJ1dGUoJ25hbWUnKSArICc9JykgOiAnJykgKyB0aGlzLmdldEF0dHJpYnV0ZSgndmFsdWUnKTsgfSkuZ2V0KCkuam9pbignOyAnKTtcbiAgICAgICAgICAgIG1lZGlhICs9ICdcXHJcXG4nO1xuICAgICAgICB9XG4gICAgICAgIC8vIHhlcC0wMjkzXG4gICAgICAgIG1lZGlhICs9IHNlbGYuUnRjcEZiRnJvbUppbmdsZSgkKHRoaXMpLCB0aGlzLmdldEF0dHJpYnV0ZSgnaWQnKSk7XG4gICAgfSk7XG5cbiAgICAvLyB4ZXAtMDI5M1xuICAgIG1lZGlhICs9IHNlbGYuUnRjcEZiRnJvbUppbmdsZShkZXNjLCAnKicpO1xuXG4gICAgLy8geGVwLTAyOTRcbiAgICB0bXAgPSBkZXNjLmZpbmQoJz5ydHAtaGRyZXh0W3htbG5zPVwidXJuOnhtcHA6amluZ2xlOmFwcHM6cnRwOnJ0cC1oZHJleHQ6MFwiXScpO1xuICAgIHRtcC5lYWNoKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgbWVkaWEgKz0gJ2E9ZXh0bWFwOicgKyB0aGlzLmdldEF0dHJpYnV0ZSgnaWQnKSArICcgJyArIHRoaXMuZ2V0QXR0cmlidXRlKCd1cmknKSArICdcXHJcXG4nO1xuICAgIH0pO1xuXG4gICAgY29udGVudC5maW5kKCc+dHJhbnNwb3J0W3htbG5zPVwidXJuOnhtcHA6amluZ2xlOnRyYW5zcG9ydHM6aWNlLXVkcDoxXCJdPmNhbmRpZGF0ZScpLmVhY2goZnVuY3Rpb24gKCkge1xuICAgICAgICBtZWRpYSArPSBTRFBVdGlsLmNhbmRpZGF0ZUZyb21KaW5nbGUodGhpcyk7XG4gICAgfSk7XG5cbiAgICAvLyBYRVAtMDMzOSBoYW5kbGUgc3NyYy1ncm91cCBhdHRyaWJ1dGVzXG4gICAgdG1wID0gY29udGVudC5maW5kKCdkZXNjcmlwdGlvbj5zc3JjLWdyb3VwW3htbG5zPVwidXJuOnhtcHA6amluZ2xlOmFwcHM6cnRwOnNzbWE6MFwiXScpLmVhY2goZnVuY3Rpb24oKSB7XG4gICAgICAgIHZhciBzZW1hbnRpY3MgPSB0aGlzLmdldEF0dHJpYnV0ZSgnc2VtYW50aWNzJyk7XG4gICAgICAgIHZhciBzc3JjcyA9ICQodGhpcykuZmluZCgnPnNvdXJjZScpLm1hcChmdW5jdGlvbigpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmdldEF0dHJpYnV0ZSgnc3NyYycpO1xuICAgICAgICB9KS5nZXQoKTtcblxuICAgICAgICBpZiAoc3NyY3MubGVuZ3RoICE9IDApIHtcbiAgICAgICAgICAgIG1lZGlhICs9ICdhPXNzcmMtZ3JvdXA6JyArIHNlbWFudGljcyArICcgJyArIHNzcmNzLmpvaW4oJyAnKSArICdcXHJcXG4nO1xuICAgICAgICB9XG4gICAgfSk7XG5cbiAgICB0bXAgPSBjb250ZW50LmZpbmQoJ2Rlc2NyaXB0aW9uPnNvdXJjZVt4bWxucz1cInVybjp4bXBwOmppbmdsZTphcHBzOnJ0cDpzc21hOjBcIl0nKTtcbiAgICB0bXAuZWFjaChmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBzc3JjID0gdGhpcy5nZXRBdHRyaWJ1dGUoJ3NzcmMnKTtcbiAgICAgICAgJCh0aGlzKS5maW5kKCc+cGFyYW1ldGVyJykuZWFjaChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICBtZWRpYSArPSAnYT1zc3JjOicgKyBzc3JjICsgJyAnICsgdGhpcy5nZXRBdHRyaWJ1dGUoJ25hbWUnKTtcbiAgICAgICAgICAgIGlmICh0aGlzLmdldEF0dHJpYnV0ZSgndmFsdWUnKSAmJiB0aGlzLmdldEF0dHJpYnV0ZSgndmFsdWUnKS5sZW5ndGgpXG4gICAgICAgICAgICAgICAgbWVkaWEgKz0gJzonICsgdGhpcy5nZXRBdHRyaWJ1dGUoJ3ZhbHVlJyk7XG4gICAgICAgICAgICBtZWRpYSArPSAnXFxyXFxuJztcbiAgICAgICAgfSk7XG4gICAgfSk7XG5cbiAgICByZXR1cm4gbWVkaWE7XG59O1xuXG5cbm1vZHVsZS5leHBvcnRzID0gU0RQO1xuXG4iLCJmdW5jdGlvbiBTRFBEaWZmZXIobXlTRFAsIG90aGVyU0RQKSB7XG4gICAgdGhpcy5teVNEUCA9IG15U0RQO1xuICAgIHRoaXMub3RoZXJTRFAgPSBvdGhlclNEUDtcbn1cblxuLyoqXG4gKiBSZXR1cm5zIG1hcCBvZiBNZWRpYUNoYW5uZWwgdGhhdCBjb250YWlucyBvbmx5IG1lZGlhIG5vdCBjb250YWluZWQgaW4gPHR0Pm90aGVyU2RwPC90dD4uIE1hcHBlZCBieSBjaGFubmVsIGlkeC5cbiAqIEBwYXJhbSBvdGhlclNkcCB0aGUgb3RoZXIgU0RQIHRvIGNoZWNrIHNzcmMgd2l0aC5cbiAqL1xuU0RQRGlmZmVyLnByb3RvdHlwZS5nZXROZXdNZWRpYSA9IGZ1bmN0aW9uKCkge1xuXG4gICAgLy8gdGhpcyBjb3VsZCBiZSB1c2VmdWwgaW4gQXJyYXkucHJvdG90eXBlLlxuICAgIGZ1bmN0aW9uIGFycmF5RXF1YWxzKGFycmF5KSB7XG4gICAgICAgIC8vIGlmIHRoZSBvdGhlciBhcnJheSBpcyBhIGZhbHN5IHZhbHVlLCByZXR1cm5cbiAgICAgICAgaWYgKCFhcnJheSlcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcblxuICAgICAgICAvLyBjb21wYXJlIGxlbmd0aHMgLSBjYW4gc2F2ZSBhIGxvdCBvZiB0aW1lXG4gICAgICAgIGlmICh0aGlzLmxlbmd0aCAhPSBhcnJheS5sZW5ndGgpXG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIGw9dGhpcy5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgICAgIC8vIENoZWNrIGlmIHdlIGhhdmUgbmVzdGVkIGFycmF5c1xuICAgICAgICAgICAgaWYgKHRoaXNbaV0gaW5zdGFuY2VvZiBBcnJheSAmJiBhcnJheVtpXSBpbnN0YW5jZW9mIEFycmF5KSB7XG4gICAgICAgICAgICAgICAgLy8gcmVjdXJzZSBpbnRvIHRoZSBuZXN0ZWQgYXJyYXlzXG4gICAgICAgICAgICAgICAgaWYgKCF0aGlzW2ldLmVxdWFscyhhcnJheVtpXSkpXG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKHRoaXNbaV0gIT0gYXJyYXlbaV0pIHtcbiAgICAgICAgICAgICAgICAvLyBXYXJuaW5nIC0gdHdvIGRpZmZlcmVudCBvYmplY3QgaW5zdGFuY2VzIHdpbGwgbmV2ZXIgYmUgZXF1YWw6IHt4OjIwfSAhPSB7eDoyMH1cbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgdmFyIG15TWVkaWFzID0gdGhpcy5teVNEUC5nZXRNZWRpYVNzcmNNYXAoKTtcbiAgICB2YXIgb3RoZXJzTWVkaWFzID0gdGhpcy5vdGhlclNEUC5nZXRNZWRpYVNzcmNNYXAoKTtcbiAgICB2YXIgbmV3TWVkaWEgPSB7fTtcbiAgICBPYmplY3Qua2V5cyhvdGhlcnNNZWRpYXMpLmZvckVhY2goZnVuY3Rpb24ob3RoZXJzTWVkaWFJZHgpIHtcbiAgICAgICAgdmFyIG15TWVkaWEgPSBteU1lZGlhc1tvdGhlcnNNZWRpYUlkeF07XG4gICAgICAgIHZhciBvdGhlcnNNZWRpYSA9IG90aGVyc01lZGlhc1tvdGhlcnNNZWRpYUlkeF07XG4gICAgICAgIGlmKCFteU1lZGlhICYmIG90aGVyc01lZGlhKSB7XG4gICAgICAgICAgICAvLyBBZGQgd2hvbGUgY2hhbm5lbFxuICAgICAgICAgICAgbmV3TWVkaWFbb3RoZXJzTWVkaWFJZHhdID0gb3RoZXJzTWVkaWE7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgLy8gTG9vayBmb3IgbmV3IHNzcmNzIGFjY3Jvc3MgdGhlIGNoYW5uZWxcbiAgICAgICAgT2JqZWN0LmtleXMob3RoZXJzTWVkaWEuc3NyY3MpLmZvckVhY2goZnVuY3Rpb24oc3NyYykge1xuICAgICAgICAgICAgaWYoT2JqZWN0LmtleXMobXlNZWRpYS5zc3JjcykuaW5kZXhPZihzc3JjKSA9PT0gLTEpIHtcbiAgICAgICAgICAgICAgICAvLyBBbGxvY2F0ZSBjaGFubmVsIGlmIHdlJ3ZlIGZvdW5kIHNzcmMgdGhhdCBkb2Vzbid0IGV4aXN0IGluIG91ciBjaGFubmVsXG4gICAgICAgICAgICAgICAgaWYoIW5ld01lZGlhW290aGVyc01lZGlhSWR4XSl7XG4gICAgICAgICAgICAgICAgICAgIG5ld01lZGlhW290aGVyc01lZGlhSWR4XSA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG1lZGlhaW5kZXg6IG90aGVyc01lZGlhLm1lZGlhaW5kZXgsXG4gICAgICAgICAgICAgICAgICAgICAgICBtaWQ6IG90aGVyc01lZGlhLm1pZCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNzcmNzOiB7fSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNzcmNHcm91cHM6IFtdXG4gICAgICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIG5ld01lZGlhW290aGVyc01lZGlhSWR4XS5zc3Jjc1tzc3JjXSA9IG90aGVyc01lZGlhLnNzcmNzW3NzcmNdO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgICAvLyBMb29rIGZvciBuZXcgc3NyYyBncm91cHMgYWNyb3NzIHRoZSBjaGFubmVsc1xuICAgICAgICBvdGhlcnNNZWRpYS5zc3JjR3JvdXBzLmZvckVhY2goZnVuY3Rpb24ob3RoZXJTc3JjR3JvdXApe1xuXG4gICAgICAgICAgICAvLyB0cnkgdG8gbWF0Y2ggdGhlIG90aGVyIHNzcmMtZ3JvdXAgd2l0aCBhbiBzc3JjLWdyb3VwIG9mIG91cnNcbiAgICAgICAgICAgIHZhciBtYXRjaGVkID0gZmFsc2U7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IG15TWVkaWEuc3NyY0dyb3Vwcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIHZhciBteVNzcmNHcm91cCA9IG15TWVkaWEuc3NyY0dyb3Vwc1tpXTtcbiAgICAgICAgICAgICAgICBpZiAob3RoZXJTc3JjR3JvdXAuc2VtYW50aWNzID09IG15U3NyY0dyb3VwLnNlbWFudGljc1xuICAgICAgICAgICAgICAgICAgICAmJiBhcnJheUVxdWFscy5hcHBseShvdGhlclNzcmNHcm91cC5zc3JjcywgW215U3NyY0dyb3VwLnNzcmNzXSkpIHtcblxuICAgICAgICAgICAgICAgICAgICBtYXRjaGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoIW1hdGNoZWQpIHtcbiAgICAgICAgICAgICAgICAvLyBBbGxvY2F0ZSBjaGFubmVsIGlmIHdlJ3ZlIGZvdW5kIGFuIHNzcmMtZ3JvdXAgdGhhdCBkb2Vzbid0XG4gICAgICAgICAgICAgICAgLy8gZXhpc3QgaW4gb3VyIGNoYW5uZWxcblxuICAgICAgICAgICAgICAgIGlmKCFuZXdNZWRpYVtvdGhlcnNNZWRpYUlkeF0pe1xuICAgICAgICAgICAgICAgICAgICBuZXdNZWRpYVtvdGhlcnNNZWRpYUlkeF0gPSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBtZWRpYWluZGV4OiBvdGhlcnNNZWRpYS5tZWRpYWluZGV4LFxuICAgICAgICAgICAgICAgICAgICAgICAgbWlkOiBvdGhlcnNNZWRpYS5taWQsXG4gICAgICAgICAgICAgICAgICAgICAgICBzc3Jjczoge30sXG4gICAgICAgICAgICAgICAgICAgICAgICBzc3JjR3JvdXBzOiBbXVxuICAgICAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBuZXdNZWRpYVtvdGhlcnNNZWRpYUlkeF0uc3NyY0dyb3Vwcy5wdXNoKG90aGVyU3NyY0dyb3VwKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfSk7XG4gICAgcmV0dXJuIG5ld01lZGlhO1xufTtcblxuLyoqXG4gKiBTZW5kcyBTU1JDIHVwZGF0ZSBJUS5cbiAqIEBwYXJhbSBzZHBNZWRpYVNzcmNzIFNTUkNzIG1hcCBvYnRhaW5lZCBmcm9tIFNEUC5nZXROZXdNZWRpYS4gQ250YWlucyBTU1JDcyB0byBhZGQvcmVtb3ZlLlxuICogQHBhcmFtIHNpZCBzZXNzaW9uIGlkZW50aWZpZXIgdGhhdCB3aWxsIGJlIHB1dCBpbnRvIHRoZSBJUS5cbiAqIEBwYXJhbSBpbml0aWF0b3IgaW5pdGlhdG9yIGlkZW50aWZpZXIuXG4gKiBAcGFyYW0gdG9KaWQgZGVzdGluYXRpb24gSmlkXG4gKiBAcGFyYW0gaXNBZGQgaW5kaWNhdGVzIGlmIHRoaXMgaXMgcmVtb3ZlIG9yIGFkZCBvcGVyYXRpb24uXG4gKi9cblNEUERpZmZlci5wcm90b3R5cGUudG9KaW5nbGUgPSBmdW5jdGlvbihtb2RpZnkpIHtcbiAgICB2YXIgc2RwTWVkaWFTc3JjcyA9IHRoaXMuZ2V0TmV3TWVkaWEoKTtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG5cbiAgICAvLyBGSVhNRTogb25seSBhbm5vdW5jZSB2aWRlbyBzc3JjcyBzaW5jZSB3ZSBtaXggYXVkaW8gYW5kIGRvbnQgbmVlZFxuICAgIC8vICAgICAgdGhlIGF1ZGlvIHNzcmNzIHRoZXJlZm9yZVxuICAgIHZhciBtb2RpZmllZCA9IGZhbHNlO1xuICAgIE9iamVjdC5rZXlzKHNkcE1lZGlhU3NyY3MpLmZvckVhY2goZnVuY3Rpb24obWVkaWFpbmRleCl7XG4gICAgICAgIG1vZGlmaWVkID0gdHJ1ZTtcbiAgICAgICAgdmFyIG1lZGlhID0gc2RwTWVkaWFTc3Jjc1ttZWRpYWluZGV4XTtcbiAgICAgICAgbW9kaWZ5LmMoJ2NvbnRlbnQnLCB7bmFtZTogbWVkaWEubWlkfSk7XG5cbiAgICAgICAgbW9kaWZ5LmMoJ2Rlc2NyaXB0aW9uJywge3htbG5zOid1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6MScsIG1lZGlhOiBtZWRpYS5taWR9KTtcbiAgICAgICAgLy8gRklYTUU6IG5vdCBjb21wbGV0bHkgc3VyZSB0aGlzIG9wZXJhdGVzIG9uIGJsb2NrcyBhbmQgLyBvciBoYW5kbGVzIGRpZmZlcmVudCBzc3JjcyBjb3JyZWN0bHlcbiAgICAgICAgLy8gZ2VuZXJhdGUgc291cmNlcyBmcm9tIGxpbmVzXG4gICAgICAgIE9iamVjdC5rZXlzKG1lZGlhLnNzcmNzKS5mb3JFYWNoKGZ1bmN0aW9uKHNzcmNOdW0pIHtcbiAgICAgICAgICAgIHZhciBtZWRpYVNzcmMgPSBtZWRpYS5zc3Jjc1tzc3JjTnVtXTtcbiAgICAgICAgICAgIG1vZGlmeS5jKCdzb3VyY2UnLCB7IHhtbG5zOiAndXJuOnhtcHA6amluZ2xlOmFwcHM6cnRwOnNzbWE6MCcgfSk7XG4gICAgICAgICAgICBtb2RpZnkuYXR0cnMoe3NzcmM6IG1lZGlhU3NyYy5zc3JjfSk7XG4gICAgICAgICAgICAvLyBpdGVyYXRlIG92ZXIgc3NyYyBsaW5lc1xuICAgICAgICAgICAgbWVkaWFTc3JjLmxpbmVzLmZvckVhY2goZnVuY3Rpb24gKGxpbmUpIHtcbiAgICAgICAgICAgICAgICB2YXIgaWR4ID0gbGluZS5pbmRleE9mKCcgJyk7XG4gICAgICAgICAgICAgICAgdmFyIGt2ID0gbGluZS5zdWJzdHIoaWR4ICsgMSk7XG4gICAgICAgICAgICAgICAgbW9kaWZ5LmMoJ3BhcmFtZXRlcicpO1xuICAgICAgICAgICAgICAgIGlmIChrdi5pbmRleE9mKCc6JykgPT0gLTEpIHtcbiAgICAgICAgICAgICAgICAgICAgbW9kaWZ5LmF0dHJzKHsgbmFtZToga3YgfSk7XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgbW9kaWZ5LmF0dHJzKHsgbmFtZToga3Yuc3BsaXQoJzonLCAyKVswXSB9KTtcbiAgICAgICAgICAgICAgICAgICAgbW9kaWZ5LmF0dHJzKHsgdmFsdWU6IGt2LnNwbGl0KCc6JywgMilbMV0gfSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIG1vZGlmeS51cCgpOyAvLyBlbmQgb2YgcGFyYW1ldGVyXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIG1vZGlmeS51cCgpOyAvLyBlbmQgb2Ygc291cmNlXG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIGdlbmVyYXRlIHNvdXJjZSBncm91cHMgZnJvbSBsaW5lc1xuICAgICAgICBtZWRpYS5zc3JjR3JvdXBzLmZvckVhY2goZnVuY3Rpb24oc3NyY0dyb3VwKSB7XG4gICAgICAgICAgICBpZiAoc3NyY0dyb3VwLnNzcmNzLmxlbmd0aCAhPSAwKSB7XG5cbiAgICAgICAgICAgICAgICBtb2RpZnkuYygnc3NyYy1ncm91cCcsIHtcbiAgICAgICAgICAgICAgICAgICAgc2VtYW50aWNzOiBzc3JjR3JvdXAuc2VtYW50aWNzLFxuICAgICAgICAgICAgICAgICAgICB4bWxuczogJ3Vybjp4bXBwOmppbmdsZTphcHBzOnJ0cDpzc21hOjAnXG4gICAgICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgICAgICBzc3JjR3JvdXAuc3NyY3MuZm9yRWFjaChmdW5jdGlvbiAoc3NyYykge1xuICAgICAgICAgICAgICAgICAgICBtb2RpZnkuYygnc291cmNlJywgeyBzc3JjOiBzc3JjIH0pXG4gICAgICAgICAgICAgICAgICAgICAgICAudXAoKTsgLy8gZW5kIG9mIHNvdXJjZVxuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIG1vZGlmeS51cCgpOyAvLyBlbmQgb2Ygc3NyYy1ncm91cFxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgICBtb2RpZnkudXAoKTsgLy8gZW5kIG9mIGRlc2NyaXB0aW9uXG4gICAgICAgIG1vZGlmeS51cCgpOyAvLyBlbmQgb2YgY29udGVudFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIG1vZGlmaWVkO1xufTtcblxubW9kdWxlLmV4cG9ydHMgPSBTRFBEaWZmZXI7IiwiU0RQVXRpbCA9IHtcbiAgICBpY2VwYXJhbXM6IGZ1bmN0aW9uIChtZWRpYWRlc2MsIHNlc3Npb25kZXNjKSB7XG4gICAgICAgIHZhciBkYXRhID0gbnVsbDtcbiAgICAgICAgaWYgKFNEUFV0aWwuZmluZF9saW5lKG1lZGlhZGVzYywgJ2E9aWNlLXVmcmFnOicsIHNlc3Npb25kZXNjKSAmJlxuICAgICAgICAgICAgU0RQVXRpbC5maW5kX2xpbmUobWVkaWFkZXNjLCAnYT1pY2UtcHdkOicsIHNlc3Npb25kZXNjKSkge1xuICAgICAgICAgICAgZGF0YSA9IHtcbiAgICAgICAgICAgICAgICB1ZnJhZzogU0RQVXRpbC5wYXJzZV9pY2V1ZnJhZyhTRFBVdGlsLmZpbmRfbGluZShtZWRpYWRlc2MsICdhPWljZS11ZnJhZzonLCBzZXNzaW9uZGVzYykpLFxuICAgICAgICAgICAgICAgIHB3ZDogU0RQVXRpbC5wYXJzZV9pY2Vwd2QoU0RQVXRpbC5maW5kX2xpbmUobWVkaWFkZXNjLCAnYT1pY2UtcHdkOicsIHNlc3Npb25kZXNjKSlcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGRhdGE7XG4gICAgfSxcbiAgICBwYXJzZV9pY2V1ZnJhZzogZnVuY3Rpb24gKGxpbmUpIHtcbiAgICAgICAgcmV0dXJuIGxpbmUuc3Vic3RyaW5nKDEyKTtcbiAgICB9LFxuICAgIGJ1aWxkX2ljZXVmcmFnOiBmdW5jdGlvbiAoZnJhZykge1xuICAgICAgICByZXR1cm4gJ2E9aWNlLXVmcmFnOicgKyBmcmFnO1xuICAgIH0sXG4gICAgcGFyc2VfaWNlcHdkOiBmdW5jdGlvbiAobGluZSkge1xuICAgICAgICByZXR1cm4gbGluZS5zdWJzdHJpbmcoMTApO1xuICAgIH0sXG4gICAgYnVpbGRfaWNlcHdkOiBmdW5jdGlvbiAocHdkKSB7XG4gICAgICAgIHJldHVybiAnYT1pY2UtcHdkOicgKyBwd2Q7XG4gICAgfSxcbiAgICBwYXJzZV9taWQ6IGZ1bmN0aW9uIChsaW5lKSB7XG4gICAgICAgIHJldHVybiBsaW5lLnN1YnN0cmluZyg2KTtcbiAgICB9LFxuICAgIHBhcnNlX21saW5lOiBmdW5jdGlvbiAobGluZSkge1xuICAgICAgICB2YXIgcGFydHMgPSBsaW5lLnN1YnN0cmluZygyKS5zcGxpdCgnICcpLFxuICAgICAgICAgICAgZGF0YSA9IHt9O1xuICAgICAgICBkYXRhLm1lZGlhID0gcGFydHMuc2hpZnQoKTtcbiAgICAgICAgZGF0YS5wb3J0ID0gcGFydHMuc2hpZnQoKTtcbiAgICAgICAgZGF0YS5wcm90byA9IHBhcnRzLnNoaWZ0KCk7XG4gICAgICAgIGlmIChwYXJ0c1twYXJ0cy5sZW5ndGggLSAxXSA9PT0gJycpIHsgLy8gdHJhaWxpbmcgd2hpdGVzcGFjZVxuICAgICAgICAgICAgcGFydHMucG9wKCk7XG4gICAgICAgIH1cbiAgICAgICAgZGF0YS5mbXQgPSBwYXJ0cztcbiAgICAgICAgcmV0dXJuIGRhdGE7XG4gICAgfSxcbiAgICBidWlsZF9tbGluZTogZnVuY3Rpb24gKG1saW5lKSB7XG4gICAgICAgIHJldHVybiAnbT0nICsgbWxpbmUubWVkaWEgKyAnICcgKyBtbGluZS5wb3J0ICsgJyAnICsgbWxpbmUucHJvdG8gKyAnICcgKyBtbGluZS5mbXQuam9pbignICcpO1xuICAgIH0sXG4gICAgcGFyc2VfcnRwbWFwOiBmdW5jdGlvbiAobGluZSkge1xuICAgICAgICB2YXIgcGFydHMgPSBsaW5lLnN1YnN0cmluZyg5KS5zcGxpdCgnICcpLFxuICAgICAgICAgICAgZGF0YSA9IHt9O1xuICAgICAgICBkYXRhLmlkID0gcGFydHMuc2hpZnQoKTtcbiAgICAgICAgcGFydHMgPSBwYXJ0c1swXS5zcGxpdCgnLycpO1xuICAgICAgICBkYXRhLm5hbWUgPSBwYXJ0cy5zaGlmdCgpO1xuICAgICAgICBkYXRhLmNsb2NrcmF0ZSA9IHBhcnRzLnNoaWZ0KCk7XG4gICAgICAgIGRhdGEuY2hhbm5lbHMgPSBwYXJ0cy5sZW5ndGggPyBwYXJ0cy5zaGlmdCgpIDogJzEnO1xuICAgICAgICByZXR1cm4gZGF0YTtcbiAgICB9LFxuICAgIC8qKlxuICAgICAqIFBhcnNlcyBTRFAgbGluZSBcImE9c2N0cG1hcDouLi5cIiBhbmQgZXh0cmFjdHMgU0NUUCBwb3J0IGZyb20gaXQuXG4gICAgICogQHBhcmFtIGxpbmUgZWcuIFwiYT1zY3RwbWFwOjUwMDAgd2VicnRjLWRhdGFjaGFubmVsXCJcbiAgICAgKiBAcmV0dXJucyBbU0NUUCBwb3J0IG51bWJlciwgcHJvdG9jb2wsIHN0cmVhbXNdXG4gICAgICovXG4gICAgcGFyc2Vfc2N0cG1hcDogZnVuY3Rpb24gKGxpbmUpXG4gICAge1xuICAgICAgICB2YXIgcGFydHMgPSBsaW5lLnN1YnN0cmluZygxMCkuc3BsaXQoJyAnKTtcbiAgICAgICAgdmFyIHNjdHBQb3J0ID0gcGFydHNbMF07XG4gICAgICAgIHZhciBwcm90b2NvbCA9IHBhcnRzWzFdO1xuICAgICAgICAvLyBTdHJlYW0gY291bnQgaXMgb3B0aW9uYWxcbiAgICAgICAgdmFyIHN0cmVhbUNvdW50ID0gcGFydHMubGVuZ3RoID4gMiA/IHBhcnRzWzJdIDogbnVsbDtcbiAgICAgICAgcmV0dXJuIFtzY3RwUG9ydCwgcHJvdG9jb2wsIHN0cmVhbUNvdW50XTsvLyBTQ1RQIHBvcnRcbiAgICB9LFxuICAgIGJ1aWxkX3J0cG1hcDogZnVuY3Rpb24gKGVsKSB7XG4gICAgICAgIHZhciBsaW5lID0gJ2E9cnRwbWFwOicgKyBlbC5nZXRBdHRyaWJ1dGUoJ2lkJykgKyAnICcgKyBlbC5nZXRBdHRyaWJ1dGUoJ25hbWUnKSArICcvJyArIGVsLmdldEF0dHJpYnV0ZSgnY2xvY2tyYXRlJyk7XG4gICAgICAgIGlmIChlbC5nZXRBdHRyaWJ1dGUoJ2NoYW5uZWxzJykgJiYgZWwuZ2V0QXR0cmlidXRlKCdjaGFubmVscycpICE9ICcxJykge1xuICAgICAgICAgICAgbGluZSArPSAnLycgKyBlbC5nZXRBdHRyaWJ1dGUoJ2NoYW5uZWxzJyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGxpbmU7XG4gICAgfSxcbiAgICBwYXJzZV9jcnlwdG86IGZ1bmN0aW9uIChsaW5lKSB7XG4gICAgICAgIHZhciBwYXJ0cyA9IGxpbmUuc3Vic3RyaW5nKDkpLnNwbGl0KCcgJyksXG4gICAgICAgICAgICBkYXRhID0ge307XG4gICAgICAgIGRhdGEudGFnID0gcGFydHMuc2hpZnQoKTtcbiAgICAgICAgZGF0YVsnY3J5cHRvLXN1aXRlJ10gPSBwYXJ0cy5zaGlmdCgpO1xuICAgICAgICBkYXRhWydrZXktcGFyYW1zJ10gPSBwYXJ0cy5zaGlmdCgpO1xuICAgICAgICBpZiAocGFydHMubGVuZ3RoKSB7XG4gICAgICAgICAgICBkYXRhWydzZXNzaW9uLXBhcmFtcyddID0gcGFydHMuam9pbignICcpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBkYXRhO1xuICAgIH0sXG4gICAgcGFyc2VfZmluZ2VycHJpbnQ6IGZ1bmN0aW9uIChsaW5lKSB7IC8vIFJGQyA0NTcyXG4gICAgICAgIHZhciBwYXJ0cyA9IGxpbmUuc3Vic3RyaW5nKDE0KS5zcGxpdCgnICcpLFxuICAgICAgICAgICAgZGF0YSA9IHt9O1xuICAgICAgICBkYXRhLmhhc2ggPSBwYXJ0cy5zaGlmdCgpO1xuICAgICAgICBkYXRhLmZpbmdlcnByaW50ID0gcGFydHMuc2hpZnQoKTtcbiAgICAgICAgLy8gVE9ETyBhc3NlcnQgdGhhdCBmaW5nZXJwcmludCBzYXRpc2ZpZXMgMlVIRVggKihcIjpcIiAyVUhFWCkgP1xuICAgICAgICByZXR1cm4gZGF0YTtcbiAgICB9LFxuICAgIHBhcnNlX2ZtdHA6IGZ1bmN0aW9uIChsaW5lKSB7XG4gICAgICAgIHZhciBwYXJ0cyA9IGxpbmUuc3BsaXQoJyAnKSxcbiAgICAgICAgICAgIGksIGtleSwgdmFsdWUsXG4gICAgICAgICAgICBkYXRhID0gW107XG4gICAgICAgIHBhcnRzLnNoaWZ0KCk7XG4gICAgICAgIHBhcnRzID0gcGFydHMuam9pbignICcpLnNwbGl0KCc7Jyk7XG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBwYXJ0cy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAga2V5ID0gcGFydHNbaV0uc3BsaXQoJz0nKVswXTtcbiAgICAgICAgICAgIHdoaWxlIChrZXkubGVuZ3RoICYmIGtleVswXSA9PSAnICcpIHtcbiAgICAgICAgICAgICAgICBrZXkgPSBrZXkuc3Vic3RyaW5nKDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdmFsdWUgPSBwYXJ0c1tpXS5zcGxpdCgnPScpWzFdO1xuICAgICAgICAgICAgaWYgKGtleSAmJiB2YWx1ZSkge1xuICAgICAgICAgICAgICAgIGRhdGEucHVzaCh7bmFtZToga2V5LCB2YWx1ZTogdmFsdWV9KTtcbiAgICAgICAgICAgIH0gZWxzZSBpZiAoa2V5KSB7XG4gICAgICAgICAgICAgICAgLy8gcmZjIDQ3MzMgKERUTUYpIHN0eWxlIHN0dWZmXG4gICAgICAgICAgICAgICAgZGF0YS5wdXNoKHtuYW1lOiAnJywgdmFsdWU6IGtleX0pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBkYXRhO1xuICAgIH0sXG4gICAgcGFyc2VfaWNlY2FuZGlkYXRlOiBmdW5jdGlvbiAobGluZSkge1xuICAgICAgICB2YXIgY2FuZGlkYXRlID0ge30sXG4gICAgICAgICAgICBlbGVtcyA9IGxpbmUuc3BsaXQoJyAnKTtcbiAgICAgICAgY2FuZGlkYXRlLmZvdW5kYXRpb24gPSBlbGVtc1swXS5zdWJzdHJpbmcoMTIpO1xuICAgICAgICBjYW5kaWRhdGUuY29tcG9uZW50ID0gZWxlbXNbMV07XG4gICAgICAgIGNhbmRpZGF0ZS5wcm90b2NvbCA9IGVsZW1zWzJdLnRvTG93ZXJDYXNlKCk7XG4gICAgICAgIGNhbmRpZGF0ZS5wcmlvcml0eSA9IGVsZW1zWzNdO1xuICAgICAgICBjYW5kaWRhdGUuaXAgPSBlbGVtc1s0XTtcbiAgICAgICAgY2FuZGlkYXRlLnBvcnQgPSBlbGVtc1s1XTtcbiAgICAgICAgLy8gZWxlbXNbNl0gPT4gXCJ0eXBcIlxuICAgICAgICBjYW5kaWRhdGUudHlwZSA9IGVsZW1zWzddO1xuICAgICAgICBjYW5kaWRhdGUuZ2VuZXJhdGlvbiA9IDA7IC8vIGRlZmF1bHQgdmFsdWUsIG1heSBiZSBvdmVyd3JpdHRlbiBiZWxvd1xuICAgICAgICBmb3IgKHZhciBpID0gODsgaSA8IGVsZW1zLmxlbmd0aDsgaSArPSAyKSB7XG4gICAgICAgICAgICBzd2l0Y2ggKGVsZW1zW2ldKSB7XG4gICAgICAgICAgICAgICAgY2FzZSAncmFkZHInOlxuICAgICAgICAgICAgICAgICAgICBjYW5kaWRhdGVbJ3JlbC1hZGRyJ10gPSBlbGVtc1tpICsgMV07XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIGNhc2UgJ3Jwb3J0JzpcbiAgICAgICAgICAgICAgICAgICAgY2FuZGlkYXRlWydyZWwtcG9ydCddID0gZWxlbXNbaSArIDFdO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlICdnZW5lcmF0aW9uJzpcbiAgICAgICAgICAgICAgICAgICAgY2FuZGlkYXRlLmdlbmVyYXRpb24gPSBlbGVtc1tpICsgMV07XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIGNhc2UgJ3RjcHR5cGUnOlxuICAgICAgICAgICAgICAgICAgICBjYW5kaWRhdGUudGNwdHlwZSA9IGVsZW1zW2kgKyAxXTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgZGVmYXVsdDogLy8gVE9ET1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygncGFyc2VfaWNlY2FuZGlkYXRlIG5vdCB0cmFuc2xhdGluZyBcIicgKyBlbGVtc1tpXSArICdcIiA9IFwiJyArIGVsZW1zW2kgKyAxXSArICdcIicpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGNhbmRpZGF0ZS5uZXR3b3JrID0gJzEnO1xuICAgICAgICBjYW5kaWRhdGUuaWQgPSBNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zdWJzdHIoMiwgMTApOyAvLyBub3QgYXBwbGljYWJsZSB0byBTRFAgLS0gRklYTUU6IHNob3VsZCBiZSB1bmlxdWUsIG5vdCBqdXN0IHJhbmRvbVxuICAgICAgICByZXR1cm4gY2FuZGlkYXRlO1xuICAgIH0sXG4gICAgYnVpbGRfaWNlY2FuZGlkYXRlOiBmdW5jdGlvbiAoY2FuZCkge1xuICAgICAgICB2YXIgbGluZSA9IFsnYT1jYW5kaWRhdGU6JyArIGNhbmQuZm91bmRhdGlvbiwgY2FuZC5jb21wb25lbnQsIGNhbmQucHJvdG9jb2wsIGNhbmQucHJpb3JpdHksIGNhbmQuaXAsIGNhbmQucG9ydCwgJ3R5cCcsIGNhbmQudHlwZV0uam9pbignICcpO1xuICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgc3dpdGNoIChjYW5kLnR5cGUpIHtcbiAgICAgICAgICAgIGNhc2UgJ3NyZmx4JzpcbiAgICAgICAgICAgIGNhc2UgJ3ByZmx4JzpcbiAgICAgICAgICAgIGNhc2UgJ3JlbGF5JzpcbiAgICAgICAgICAgICAgICBpZiAoY2FuZC5oYXNPd25BdHRyaWJ1dGUoJ3JlbC1hZGRyJykgJiYgY2FuZC5oYXNPd25BdHRyaWJ1dGUoJ3JlbC1wb3J0JykpIHtcbiAgICAgICAgICAgICAgICAgICAgbGluZSArPSAncmFkZHInO1xuICAgICAgICAgICAgICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgICAgICAgICAgICAgbGluZSArPSBjYW5kWydyZWwtYWRkciddO1xuICAgICAgICAgICAgICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgICAgICAgICAgICAgbGluZSArPSAncnBvcnQnO1xuICAgICAgICAgICAgICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgICAgICAgICAgICAgbGluZSArPSBjYW5kWydyZWwtcG9ydCddO1xuICAgICAgICAgICAgICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNhbmQuaGFzT3duQXR0cmlidXRlKCd0Y3B0eXBlJykpIHtcbiAgICAgICAgICAgIGxpbmUgKz0gJ3RjcHR5cGUnO1xuICAgICAgICAgICAgbGluZSArPSAnICc7XG4gICAgICAgICAgICBsaW5lICs9IGNhbmQudGNwdHlwZTtcbiAgICAgICAgICAgIGxpbmUgKz0gJyAnO1xuICAgICAgICB9XG4gICAgICAgIGxpbmUgKz0gJ2dlbmVyYXRpb24nO1xuICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgbGluZSArPSBjYW5kLmhhc093bkF0dHJpYnV0ZSgnZ2VuZXJhdGlvbicpID8gY2FuZC5nZW5lcmF0aW9uIDogJzAnO1xuICAgICAgICByZXR1cm4gbGluZTtcbiAgICB9LFxuICAgIHBhcnNlX3NzcmM6IGZ1bmN0aW9uIChkZXNjKSB7XG4gICAgICAgIC8vIHByb3ByaWV0YXJ5IG1hcHBpbmcgb2YgYT1zc3JjIGxpbmVzXG4gICAgICAgIC8vIFRPRE86IHNlZSBcIkppbmdsZSBSVFAgU291cmNlIERlc2NyaXB0aW9uXCIgYnkgSnViZXJ0aSBhbmQgUC4gVGhhdGNoZXIgb24gZ29vZ2xlIGRvY3NcbiAgICAgICAgLy8gYW5kIHBhcnNlIGFjY29yZGluZyB0byB0aGF0XG4gICAgICAgIHZhciBsaW5lcyA9IGRlc2Muc3BsaXQoJ1xcclxcbicpLFxuICAgICAgICAgICAgZGF0YSA9IHt9O1xuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxpbmVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpZiAobGluZXNbaV0uc3Vic3RyaW5nKDAsIDcpID09ICdhPXNzcmM6Jykge1xuICAgICAgICAgICAgICAgIHZhciBpZHggPSBsaW5lc1tpXS5pbmRleE9mKCcgJyk7XG4gICAgICAgICAgICAgICAgZGF0YVtsaW5lc1tpXS5zdWJzdHIoaWR4ICsgMSkuc3BsaXQoJzonLCAyKVswXV0gPSBsaW5lc1tpXS5zdWJzdHIoaWR4ICsgMSkuc3BsaXQoJzonLCAyKVsxXTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZGF0YTtcbiAgICB9LFxuICAgIHBhcnNlX3J0Y3BmYjogZnVuY3Rpb24gKGxpbmUpIHtcbiAgICAgICAgdmFyIHBhcnRzID0gbGluZS5zdWJzdHIoMTApLnNwbGl0KCcgJyk7XG4gICAgICAgIHZhciBkYXRhID0ge307XG4gICAgICAgIGRhdGEucHQgPSBwYXJ0cy5zaGlmdCgpO1xuICAgICAgICBkYXRhLnR5cGUgPSBwYXJ0cy5zaGlmdCgpO1xuICAgICAgICBkYXRhLnBhcmFtcyA9IHBhcnRzO1xuICAgICAgICByZXR1cm4gZGF0YTtcbiAgICB9LFxuICAgIHBhcnNlX2V4dG1hcDogZnVuY3Rpb24gKGxpbmUpIHtcbiAgICAgICAgdmFyIHBhcnRzID0gbGluZS5zdWJzdHIoOSkuc3BsaXQoJyAnKTtcbiAgICAgICAgdmFyIGRhdGEgPSB7fTtcbiAgICAgICAgZGF0YS52YWx1ZSA9IHBhcnRzLnNoaWZ0KCk7XG4gICAgICAgIGlmIChkYXRhLnZhbHVlLmluZGV4T2YoJy8nKSAhPSAtMSkge1xuICAgICAgICAgICAgZGF0YS5kaXJlY3Rpb24gPSBkYXRhLnZhbHVlLnN1YnN0cihkYXRhLnZhbHVlLmluZGV4T2YoJy8nKSArIDEpO1xuICAgICAgICAgICAgZGF0YS52YWx1ZSA9IGRhdGEudmFsdWUuc3Vic3RyKDAsIGRhdGEudmFsdWUuaW5kZXhPZignLycpKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGRhdGEuZGlyZWN0aW9uID0gJ2JvdGgnO1xuICAgICAgICB9XG4gICAgICAgIGRhdGEudXJpID0gcGFydHMuc2hpZnQoKTtcbiAgICAgICAgZGF0YS5wYXJhbXMgPSBwYXJ0cztcbiAgICAgICAgcmV0dXJuIGRhdGE7XG4gICAgfSxcbiAgICBmaW5kX2xpbmU6IGZ1bmN0aW9uIChoYXlzdGFjaywgbmVlZGxlLCBzZXNzaW9ucGFydCkge1xuICAgICAgICB2YXIgbGluZXMgPSBoYXlzdGFjay5zcGxpdCgnXFxyXFxuJyk7XG4gICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbGluZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChsaW5lc1tpXS5zdWJzdHJpbmcoMCwgbmVlZGxlLmxlbmd0aCkgPT0gbmVlZGxlKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGxpbmVzW2ldO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmICghc2Vzc2lvbnBhcnQpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgICAvLyBzZWFyY2ggc2Vzc2lvbiBwYXJ0XG4gICAgICAgIGxpbmVzID0gc2Vzc2lvbnBhcnQuc3BsaXQoJ1xcclxcbicpO1xuICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGxpbmVzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICBpZiAobGluZXNbal0uc3Vic3RyaW5nKDAsIG5lZWRsZS5sZW5ndGgpID09IG5lZWRsZSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBsaW5lc1tqXTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfSxcbiAgICBmaW5kX2xpbmVzOiBmdW5jdGlvbiAoaGF5c3RhY2ssIG5lZWRsZSwgc2Vzc2lvbnBhcnQpIHtcbiAgICAgICAgdmFyIGxpbmVzID0gaGF5c3RhY2suc3BsaXQoJ1xcclxcbicpLFxuICAgICAgICAgICAgbmVlZGxlcyA9IFtdO1xuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxpbmVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpZiAobGluZXNbaV0uc3Vic3RyaW5nKDAsIG5lZWRsZS5sZW5ndGgpID09IG5lZWRsZSlcbiAgICAgICAgICAgICAgICBuZWVkbGVzLnB1c2gobGluZXNbaV0pO1xuICAgICAgICB9XG4gICAgICAgIGlmIChuZWVkbGVzLmxlbmd0aCB8fCAhc2Vzc2lvbnBhcnQpIHtcbiAgICAgICAgICAgIHJldHVybiBuZWVkbGVzO1xuICAgICAgICB9XG4gICAgICAgIC8vIHNlYXJjaCBzZXNzaW9uIHBhcnRcbiAgICAgICAgbGluZXMgPSBzZXNzaW9ucGFydC5zcGxpdCgnXFxyXFxuJyk7XG4gICAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgbGluZXMubGVuZ3RoOyBqKyspIHtcbiAgICAgICAgICAgIGlmIChsaW5lc1tqXS5zdWJzdHJpbmcoMCwgbmVlZGxlLmxlbmd0aCkgPT0gbmVlZGxlKSB7XG4gICAgICAgICAgICAgICAgbmVlZGxlcy5wdXNoKGxpbmVzW2pdKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gbmVlZGxlcztcbiAgICB9LFxuICAgIGNhbmRpZGF0ZVRvSmluZ2xlOiBmdW5jdGlvbiAobGluZSkge1xuICAgICAgICAvLyBhPWNhbmRpZGF0ZToyOTc5MTY2NjYyIDEgdWRwIDIxMTM5MzcxNTEgMTkyLjE2OC4yLjEwMCA1NzY5OCB0eXAgaG9zdCBnZW5lcmF0aW9uIDBcbiAgICAgICAgLy8gICAgICA8Y2FuZGlkYXRlIGNvbXBvbmVudD0uLi4gZm91bmRhdGlvbj0uLi4gZ2VuZXJhdGlvbj0uLi4gaWQ9Li4uIGlwPS4uLiBuZXR3b3JrPS4uLiBwb3J0PS4uLiBwcmlvcml0eT0uLi4gcHJvdG9jb2w9Li4uIHR5cGU9Li4uLz5cbiAgICAgICAgaWYgKGxpbmUuaW5kZXhPZignY2FuZGlkYXRlOicpID09PSAwKSB7XG4gICAgICAgICAgICBsaW5lID0gJ2E9JyArIGxpbmU7XG4gICAgICAgIH0gZWxzZSBpZiAobGluZS5zdWJzdHJpbmcoMCwgMTIpICE9ICdhPWNhbmRpZGF0ZTonKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZygncGFyc2VDYW5kaWRhdGUgY2FsbGVkIHdpdGggYSBsaW5lIHRoYXQgaXMgbm90IGEgY2FuZGlkYXRlIGxpbmUnKTtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKGxpbmUpO1xuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGxpbmUuc3Vic3RyaW5nKGxpbmUubGVuZ3RoIC0gMikgPT0gJ1xcclxcbicpIC8vIGNob21wIGl0XG4gICAgICAgICAgICBsaW5lID0gbGluZS5zdWJzdHJpbmcoMCwgbGluZS5sZW5ndGggLSAyKTtcbiAgICAgICAgdmFyIGNhbmRpZGF0ZSA9IHt9LFxuICAgICAgICAgICAgZWxlbXMgPSBsaW5lLnNwbGl0KCcgJyksXG4gICAgICAgICAgICBpO1xuICAgICAgICBpZiAoZWxlbXNbNl0gIT0gJ3R5cCcpIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdkaWQgbm90IGZpbmQgdHlwIGluIHRoZSByaWdodCBwbGFjZScpO1xuICAgICAgICAgICAgY29uc29sZS5sb2cobGluZSk7XG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICBjYW5kaWRhdGUuZm91bmRhdGlvbiA9IGVsZW1zWzBdLnN1YnN0cmluZygxMik7XG4gICAgICAgIGNhbmRpZGF0ZS5jb21wb25lbnQgPSBlbGVtc1sxXTtcbiAgICAgICAgY2FuZGlkYXRlLnByb3RvY29sID0gZWxlbXNbMl0udG9Mb3dlckNhc2UoKTtcbiAgICAgICAgY2FuZGlkYXRlLnByaW9yaXR5ID0gZWxlbXNbM107XG4gICAgICAgIGNhbmRpZGF0ZS5pcCA9IGVsZW1zWzRdO1xuICAgICAgICBjYW5kaWRhdGUucG9ydCA9IGVsZW1zWzVdO1xuICAgICAgICAvLyBlbGVtc1s2XSA9PiBcInR5cFwiXG4gICAgICAgIGNhbmRpZGF0ZS50eXBlID0gZWxlbXNbN107XG5cbiAgICAgICAgY2FuZGlkYXRlLmdlbmVyYXRpb24gPSAnMCc7IC8vIGRlZmF1bHQsIG1heSBiZSBvdmVyd3JpdHRlbiBiZWxvd1xuICAgICAgICBmb3IgKGkgPSA4OyBpIDwgZWxlbXMubGVuZ3RoOyBpICs9IDIpIHtcbiAgICAgICAgICAgIHN3aXRjaCAoZWxlbXNbaV0pIHtcbiAgICAgICAgICAgICAgICBjYXNlICdyYWRkcic6XG4gICAgICAgICAgICAgICAgICAgIGNhbmRpZGF0ZVsncmVsLWFkZHInXSA9IGVsZW1zW2kgKyAxXTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgY2FzZSAncnBvcnQnOlxuICAgICAgICAgICAgICAgICAgICBjYW5kaWRhdGVbJ3JlbC1wb3J0J10gPSBlbGVtc1tpICsgMV07XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIGNhc2UgJ2dlbmVyYXRpb24nOlxuICAgICAgICAgICAgICAgICAgICBjYW5kaWRhdGUuZ2VuZXJhdGlvbiA9IGVsZW1zW2kgKyAxXTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgY2FzZSAndGNwdHlwZSc6XG4gICAgICAgICAgICAgICAgICAgIGNhbmRpZGF0ZS50Y3B0eXBlID0gZWxlbXNbaSArIDFdO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBkZWZhdWx0OiAvLyBUT0RPXG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCdub3QgdHJhbnNsYXRpbmcgXCInICsgZWxlbXNbaV0gKyAnXCIgPSBcIicgKyBlbGVtc1tpICsgMV0gKyAnXCInKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBjYW5kaWRhdGUubmV0d29yayA9ICcxJztcbiAgICAgICAgY2FuZGlkYXRlLmlkID0gTWF0aC5yYW5kb20oKS50b1N0cmluZygzNikuc3Vic3RyKDIsIDEwKTsgLy8gbm90IGFwcGxpY2FibGUgdG8gU0RQIC0tIEZJWE1FOiBzaG91bGQgYmUgdW5pcXVlLCBub3QganVzdCByYW5kb21cbiAgICAgICAgcmV0dXJuIGNhbmRpZGF0ZTtcbiAgICB9LFxuICAgIGNhbmRpZGF0ZUZyb21KaW5nbGU6IGZ1bmN0aW9uIChjYW5kKSB7XG4gICAgICAgIHZhciBsaW5lID0gJ2E9Y2FuZGlkYXRlOic7XG4gICAgICAgIGxpbmUgKz0gY2FuZC5nZXRBdHRyaWJ1dGUoJ2ZvdW5kYXRpb24nKTtcbiAgICAgICAgbGluZSArPSAnICc7XG4gICAgICAgIGxpbmUgKz0gY2FuZC5nZXRBdHRyaWJ1dGUoJ2NvbXBvbmVudCcpO1xuICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgbGluZSArPSBjYW5kLmdldEF0dHJpYnV0ZSgncHJvdG9jb2wnKTsgLy8udG9VcHBlckNhc2UoKTsgLy8gY2hyb21lIE0yMyBkb2Vzbid0IGxpa2UgdGhpc1xuICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgbGluZSArPSBjYW5kLmdldEF0dHJpYnV0ZSgncHJpb3JpdHknKTtcbiAgICAgICAgbGluZSArPSAnICc7XG4gICAgICAgIGxpbmUgKz0gY2FuZC5nZXRBdHRyaWJ1dGUoJ2lwJyk7XG4gICAgICAgIGxpbmUgKz0gJyAnO1xuICAgICAgICBsaW5lICs9IGNhbmQuZ2V0QXR0cmlidXRlKCdwb3J0Jyk7XG4gICAgICAgIGxpbmUgKz0gJyAnO1xuICAgICAgICBsaW5lICs9ICd0eXAnO1xuICAgICAgICBsaW5lICs9ICcgJyArIGNhbmQuZ2V0QXR0cmlidXRlKCd0eXBlJyk7XG4gICAgICAgIGxpbmUgKz0gJyAnO1xuICAgICAgICBzd2l0Y2ggKGNhbmQuZ2V0QXR0cmlidXRlKCd0eXBlJykpIHtcbiAgICAgICAgICAgIGNhc2UgJ3NyZmx4JzpcbiAgICAgICAgICAgIGNhc2UgJ3ByZmx4JzpcbiAgICAgICAgICAgIGNhc2UgJ3JlbGF5JzpcbiAgICAgICAgICAgICAgICBpZiAoY2FuZC5nZXRBdHRyaWJ1dGUoJ3JlbC1hZGRyJykgJiYgY2FuZC5nZXRBdHRyaWJ1dGUoJ3JlbC1wb3J0JykpIHtcbiAgICAgICAgICAgICAgICAgICAgbGluZSArPSAncmFkZHInO1xuICAgICAgICAgICAgICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgICAgICAgICAgICAgbGluZSArPSBjYW5kLmdldEF0dHJpYnV0ZSgncmVsLWFkZHInKTtcbiAgICAgICAgICAgICAgICAgICAgbGluZSArPSAnICc7XG4gICAgICAgICAgICAgICAgICAgIGxpbmUgKz0gJ3Jwb3J0JztcbiAgICAgICAgICAgICAgICAgICAgbGluZSArPSAnICc7XG4gICAgICAgICAgICAgICAgICAgIGxpbmUgKz0gY2FuZC5nZXRBdHRyaWJ1dGUoJ3JlbC1wb3J0Jyk7XG4gICAgICAgICAgICAgICAgICAgIGxpbmUgKz0gJyAnO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgICBpZiAoY2FuZC5nZXRBdHRyaWJ1dGUoJ3Byb3RvY29sJykudG9Mb3dlckNhc2UoKSA9PSAndGNwJykge1xuICAgICAgICAgICAgbGluZSArPSAndGNwdHlwZSc7XG4gICAgICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgICAgIGxpbmUgKz0gY2FuZC5nZXRBdHRyaWJ1dGUoJ3RjcHR5cGUnKTtcbiAgICAgICAgICAgIGxpbmUgKz0gJyAnO1xuICAgICAgICB9XG4gICAgICAgIGxpbmUgKz0gJ2dlbmVyYXRpb24nO1xuICAgICAgICBsaW5lICs9ICcgJztcbiAgICAgICAgbGluZSArPSBjYW5kLmdldEF0dHJpYnV0ZSgnZ2VuZXJhdGlvbicpIHx8ICcwJztcbiAgICAgICAgcmV0dXJuIGxpbmUgKyAnXFxyXFxuJztcbiAgICB9XG59O1xubW9kdWxlLmV4cG9ydHMgPSBTRFBVdGlsOyIsImZ1bmN0aW9uIFRyYWNlYWJsZVBlZXJDb25uZWN0aW9uKGljZV9jb25maWcsIGNvbnN0cmFpbnRzKSB7XG4gICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgIHZhciBSVENQZWVyY29ubmVjdGlvbiA9IG5hdmlnYXRvci5tb3pHZXRVc2VyTWVkaWEgPyBtb3pSVENQZWVyQ29ubmVjdGlvbiA6IHdlYmtpdFJUQ1BlZXJDb25uZWN0aW9uO1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24gPSBuZXcgUlRDUGVlcmNvbm5lY3Rpb24oaWNlX2NvbmZpZywgY29uc3RyYWludHMpO1xuICAgIHRoaXMudXBkYXRlTG9nID0gW107XG4gICAgdGhpcy5zdGF0cyA9IHt9O1xuICAgIHRoaXMuc3RhdHNpbnRlcnZhbCA9IG51bGw7XG4gICAgdGhpcy5tYXhzdGF0cyA9IDA7IC8vIGxpbWl0IHRvIDMwMCB2YWx1ZXMsIGkuZS4gNSBtaW51dGVzOyBzZXQgdG8gMCB0byBkaXNhYmxlXG5cbiAgICAvLyBvdmVycmlkZSBhcyBkZXNpcmVkXG4gICAgdGhpcy50cmFjZSA9IGZ1bmN0aW9uICh3aGF0LCBpbmZvKSB7XG4gICAgICAgIC8vY29uc29sZS53YXJuKCdXVFJBQ0UnLCB3aGF0LCBpbmZvKTtcbiAgICAgICAgc2VsZi51cGRhdGVMb2cucHVzaCh7XG4gICAgICAgICAgICB0aW1lOiBuZXcgRGF0ZSgpLFxuICAgICAgICAgICAgdHlwZTogd2hhdCxcbiAgICAgICAgICAgIHZhbHVlOiBpbmZvIHx8IFwiXCJcbiAgICAgICAgfSk7XG4gICAgfTtcbiAgICB0aGlzLm9uaWNlY2FuZGlkYXRlID0gbnVsbDtcbiAgICB0aGlzLnBlZXJjb25uZWN0aW9uLm9uaWNlY2FuZGlkYXRlID0gZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHNlbGYudHJhY2UoJ29uaWNlY2FuZGlkYXRlJywgSlNPTi5zdHJpbmdpZnkoZXZlbnQuY2FuZGlkYXRlLCBudWxsLCAnICcpKTtcbiAgICAgICAgaWYgKHNlbGYub25pY2VjYW5kaWRhdGUgIT09IG51bGwpIHtcbiAgICAgICAgICAgIHNlbGYub25pY2VjYW5kaWRhdGUoZXZlbnQpO1xuICAgICAgICB9XG4gICAgfTtcbiAgICB0aGlzLm9uYWRkc3RyZWFtID0gbnVsbDtcbiAgICB0aGlzLnBlZXJjb25uZWN0aW9uLm9uYWRkc3RyZWFtID0gZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHNlbGYudHJhY2UoJ29uYWRkc3RyZWFtJywgZXZlbnQuc3RyZWFtLmlkKTtcbiAgICAgICAgaWYgKHNlbGYub25hZGRzdHJlYW0gIT09IG51bGwpIHtcbiAgICAgICAgICAgIHNlbGYub25hZGRzdHJlYW0oZXZlbnQpO1xuICAgICAgICB9XG4gICAgfTtcbiAgICB0aGlzLm9ucmVtb3Zlc3RyZWFtID0gbnVsbDtcbiAgICB0aGlzLnBlZXJjb25uZWN0aW9uLm9ucmVtb3Zlc3RyZWFtID0gZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHNlbGYudHJhY2UoJ29ucmVtb3Zlc3RyZWFtJywgZXZlbnQuc3RyZWFtLmlkKTtcbiAgICAgICAgaWYgKHNlbGYub25yZW1vdmVzdHJlYW0gIT09IG51bGwpIHtcbiAgICAgICAgICAgIHNlbGYub25yZW1vdmVzdHJlYW0oZXZlbnQpO1xuICAgICAgICB9XG4gICAgfTtcbiAgICB0aGlzLm9uc2lnbmFsaW5nc3RhdGVjaGFuZ2UgPSBudWxsO1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24ub25zaWduYWxpbmdzdGF0ZWNoYW5nZSA9IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBzZWxmLnRyYWNlKCdvbnNpZ25hbGluZ3N0YXRlY2hhbmdlJywgc2VsZi5zaWduYWxpbmdTdGF0ZSk7XG4gICAgICAgIGlmIChzZWxmLm9uc2lnbmFsaW5nc3RhdGVjaGFuZ2UgIT09IG51bGwpIHtcbiAgICAgICAgICAgIHNlbGYub25zaWduYWxpbmdzdGF0ZWNoYW5nZShldmVudCk7XG4gICAgICAgIH1cbiAgICB9O1xuICAgIHRoaXMub25pY2Vjb25uZWN0aW9uc3RhdGVjaGFuZ2UgPSBudWxsO1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24ub25pY2Vjb25uZWN0aW9uc3RhdGVjaGFuZ2UgPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgc2VsZi50cmFjZSgnb25pY2Vjb25uZWN0aW9uc3RhdGVjaGFuZ2UnLCBzZWxmLmljZUNvbm5lY3Rpb25TdGF0ZSk7XG4gICAgICAgIGlmIChzZWxmLm9uaWNlY29ubmVjdGlvbnN0YXRlY2hhbmdlICE9PSBudWxsKSB7XG4gICAgICAgICAgICBzZWxmLm9uaWNlY29ubmVjdGlvbnN0YXRlY2hhbmdlKGV2ZW50KTtcbiAgICAgICAgfVxuICAgIH07XG4gICAgdGhpcy5vbm5lZ290aWF0aW9ubmVlZGVkID0gbnVsbDtcbiAgICB0aGlzLnBlZXJjb25uZWN0aW9uLm9ubmVnb3RpYXRpb25uZWVkZWQgPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgc2VsZi50cmFjZSgnb25uZWdvdGlhdGlvbm5lZWRlZCcpO1xuICAgICAgICBpZiAoc2VsZi5vbm5lZ290aWF0aW9ubmVlZGVkICE9PSBudWxsKSB7XG4gICAgICAgICAgICBzZWxmLm9ubmVnb3RpYXRpb25uZWVkZWQoZXZlbnQpO1xuICAgICAgICB9XG4gICAgfTtcbiAgICBzZWxmLm9uZGF0YWNoYW5uZWwgPSBudWxsO1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24ub25kYXRhY2hhbm5lbCA9IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBzZWxmLnRyYWNlKCdvbmRhdGFjaGFubmVsJywgZXZlbnQpO1xuICAgICAgICBpZiAoc2VsZi5vbmRhdGFjaGFubmVsICE9PSBudWxsKSB7XG4gICAgICAgICAgICBzZWxmLm9uZGF0YWNoYW5uZWwoZXZlbnQpO1xuICAgICAgICB9XG4gICAgfTtcbiAgICBpZiAoIW5hdmlnYXRvci5tb3pHZXRVc2VyTWVkaWEgJiYgdGhpcy5tYXhzdGF0cykge1xuICAgICAgICB0aGlzLnN0YXRzaW50ZXJ2YWwgPSB3aW5kb3cuc2V0SW50ZXJ2YWwoZnVuY3Rpb24oKSB7XG4gICAgICAgICAgICBzZWxmLnBlZXJjb25uZWN0aW9uLmdldFN0YXRzKGZ1bmN0aW9uKHN0YXRzKSB7XG4gICAgICAgICAgICAgICAgdmFyIHJlc3VsdHMgPSBzdGF0cy5yZXN1bHQoKTtcbiAgICAgICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHJlc3VsdHMubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgICAgICAgICAgICAgLy9jb25zb2xlLmxvZyhyZXN1bHRzW2ldLnR5cGUsIHJlc3VsdHNbaV0uaWQsIHJlc3VsdHNbaV0ubmFtZXMoKSlcbiAgICAgICAgICAgICAgICAgICAgdmFyIG5vdyA9IG5ldyBEYXRlKCk7XG4gICAgICAgICAgICAgICAgICAgIHJlc3VsdHNbaV0ubmFtZXMoKS5mb3JFYWNoKGZ1bmN0aW9uIChuYW1lKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgaWQgPSByZXN1bHRzW2ldLmlkICsgJy0nICsgbmFtZTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmICghc2VsZi5zdGF0c1tpZF0pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxmLnN0YXRzW2lkXSA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRUaW1lOiBub3csXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVuZFRpbWU6IG5vdyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzOiBbXSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXM6IFtdXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYuc3RhdHNbaWRdLnZhbHVlcy5wdXNoKHJlc3VsdHNbaV0uc3RhdChuYW1lKSk7XG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxmLnN0YXRzW2lkXS50aW1lcy5wdXNoKG5vdy5nZXRUaW1lKCkpO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHNlbGYuc3RhdHNbaWRdLnZhbHVlcy5sZW5ndGggPiBzZWxmLm1heHN0YXRzKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZi5zdGF0c1tpZF0udmFsdWVzLnNoaWZ0KCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZi5zdGF0c1tpZF0udGltZXMuc2hpZnQoKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYuc3RhdHNbaWRdLmVuZFRpbWUgPSBub3c7XG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgIH0sIDEwMDApO1xuICAgIH1cbn07XG5cbmR1bXBTRFAgPSBmdW5jdGlvbihkZXNjcmlwdGlvbikge1xuICAgIHJldHVybiAndHlwZTogJyArIGRlc2NyaXB0aW9uLnR5cGUgKyAnXFxyXFxuJyArIGRlc2NyaXB0aW9uLnNkcDtcbn1cblxuaWYgKFRyYWNlYWJsZVBlZXJDb25uZWN0aW9uLnByb3RvdHlwZS5fX2RlZmluZUdldHRlcl9fICE9PSB1bmRlZmluZWQpIHtcbiAgICBUcmFjZWFibGVQZWVyQ29ubmVjdGlvbi5wcm90b3R5cGUuX19kZWZpbmVHZXR0ZXJfXygnc2lnbmFsaW5nU3RhdGUnLCBmdW5jdGlvbigpIHsgcmV0dXJuIHRoaXMucGVlcmNvbm5lY3Rpb24uc2lnbmFsaW5nU3RhdGU7IH0pO1xuICAgIFRyYWNlYWJsZVBlZXJDb25uZWN0aW9uLnByb3RvdHlwZS5fX2RlZmluZUdldHRlcl9fKCdpY2VDb25uZWN0aW9uU3RhdGUnLCBmdW5jdGlvbigpIHsgcmV0dXJuIHRoaXMucGVlcmNvbm5lY3Rpb24uaWNlQ29ubmVjdGlvblN0YXRlOyB9KTtcbiAgICBUcmFjZWFibGVQZWVyQ29ubmVjdGlvbi5wcm90b3R5cGUuX19kZWZpbmVHZXR0ZXJfXygnbG9jYWxEZXNjcmlwdGlvbicsIGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgcHVibGljTG9jYWxEZXNjcmlwdGlvbiA9IHNpbXVsY2FzdC5yZXZlcnNlVHJhbnNmb3JtTG9jYWxEZXNjcmlwdGlvbih0aGlzLnBlZXJjb25uZWN0aW9uLmxvY2FsRGVzY3JpcHRpb24pO1xuICAgICAgICByZXR1cm4gcHVibGljTG9jYWxEZXNjcmlwdGlvbjtcbiAgICB9KTtcbiAgICBUcmFjZWFibGVQZWVyQ29ubmVjdGlvbi5wcm90b3R5cGUuX19kZWZpbmVHZXR0ZXJfXygncmVtb3RlRGVzY3JpcHRpb24nLCBmdW5jdGlvbigpIHtcbiAgICAgICAgdmFyIHB1YmxpY1JlbW90ZURlc2NyaXB0aW9uID0gc2ltdWxjYXN0LnJldmVyc2VUcmFuc2Zvcm1SZW1vdGVEZXNjcmlwdGlvbih0aGlzLnBlZXJjb25uZWN0aW9uLnJlbW90ZURlc2NyaXB0aW9uKTtcbiAgICAgICAgcmV0dXJuIHB1YmxpY1JlbW90ZURlc2NyaXB0aW9uO1xuICAgIH0pO1xufVxuXG5UcmFjZWFibGVQZWVyQ29ubmVjdGlvbi5wcm90b3R5cGUuYWRkU3RyZWFtID0gZnVuY3Rpb24gKHN0cmVhbSkge1xuICAgIHRoaXMudHJhY2UoJ2FkZFN0cmVhbScsIHN0cmVhbS5pZCk7XG4gICAgc2ltdWxjYXN0LnJlc2V0U2VuZGVyKCk7XG4gICAgdHJ5XG4gICAge1xuICAgICAgICB0aGlzLnBlZXJjb25uZWN0aW9uLmFkZFN0cmVhbShzdHJlYW0pO1xuICAgIH1cbiAgICBjYXRjaCAoZSlcbiAgICB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoZSk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG59O1xuXG5UcmFjZWFibGVQZWVyQ29ubmVjdGlvbi5wcm90b3R5cGUucmVtb3ZlU3RyZWFtID0gZnVuY3Rpb24gKHN0cmVhbSwgc3RvcFN0cmVhbXMpIHtcbiAgICB0aGlzLnRyYWNlKCdyZW1vdmVTdHJlYW0nLCBzdHJlYW0uaWQpO1xuICAgIHNpbXVsY2FzdC5yZXNldFNlbmRlcigpO1xuICAgIGlmKHN0b3BTdHJlYW1zKSB7XG4gICAgICAgIHN0cmVhbS5nZXRBdWRpb1RyYWNrcygpLmZvckVhY2goZnVuY3Rpb24gKHRyYWNrKSB7XG4gICAgICAgICAgICB0cmFjay5zdG9wKCk7XG4gICAgICAgIH0pO1xuICAgICAgICBzdHJlYW0uZ2V0VmlkZW9UcmFja3MoKS5mb3JFYWNoKGZ1bmN0aW9uICh0cmFjaykge1xuICAgICAgICAgICAgdHJhY2suc3RvcCgpO1xuICAgICAgICB9KTtcbiAgICB9XG4gICAgdGhpcy5wZWVyY29ubmVjdGlvbi5yZW1vdmVTdHJlYW0oc3RyZWFtKTtcbn07XG5cblRyYWNlYWJsZVBlZXJDb25uZWN0aW9uLnByb3RvdHlwZS5jcmVhdGVEYXRhQ2hhbm5lbCA9IGZ1bmN0aW9uIChsYWJlbCwgb3B0cykge1xuICAgIHRoaXMudHJhY2UoJ2NyZWF0ZURhdGFDaGFubmVsJywgbGFiZWwsIG9wdHMpO1xuICAgIHJldHVybiB0aGlzLnBlZXJjb25uZWN0aW9uLmNyZWF0ZURhdGFDaGFubmVsKGxhYmVsLCBvcHRzKTtcbn07XG5cblRyYWNlYWJsZVBlZXJDb25uZWN0aW9uLnByb3RvdHlwZS5zZXRMb2NhbERlc2NyaXB0aW9uID0gZnVuY3Rpb24gKGRlc2NyaXB0aW9uLCBzdWNjZXNzQ2FsbGJhY2ssIGZhaWx1cmVDYWxsYmFjaykge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICBkZXNjcmlwdGlvbiA9IHNpbXVsY2FzdC50cmFuc2Zvcm1Mb2NhbERlc2NyaXB0aW9uKGRlc2NyaXB0aW9uKTtcbiAgICB0aGlzLnRyYWNlKCdzZXRMb2NhbERlc2NyaXB0aW9uJywgZHVtcFNEUChkZXNjcmlwdGlvbikpO1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24uc2V0TG9jYWxEZXNjcmlwdGlvbihkZXNjcmlwdGlvbixcbiAgICAgICAgZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgc2VsZi50cmFjZSgnc2V0TG9jYWxEZXNjcmlwdGlvbk9uU3VjY2VzcycpO1xuICAgICAgICAgICAgc3VjY2Vzc0NhbGxiYWNrKCk7XG4gICAgICAgIH0sXG4gICAgICAgIGZ1bmN0aW9uIChlcnIpIHtcbiAgICAgICAgICAgIHNlbGYudHJhY2UoJ3NldExvY2FsRGVzY3JpcHRpb25PbkZhaWx1cmUnLCBlcnIpO1xuICAgICAgICAgICAgZmFpbHVyZUNhbGxiYWNrKGVycik7XG4gICAgICAgIH1cbiAgICApO1xuICAgIC8qXG4gICAgIGlmICh0aGlzLnN0YXRzaW50ZXJ2YWwgPT09IG51bGwgJiYgdGhpcy5tYXhzdGF0cyA+IDApIHtcbiAgICAgLy8gc3RhcnQgZ2F0aGVyaW5nIHN0YXRzXG4gICAgIH1cbiAgICAgKi9cbn07XG5cblRyYWNlYWJsZVBlZXJDb25uZWN0aW9uLnByb3RvdHlwZS5zZXRSZW1vdGVEZXNjcmlwdGlvbiA9IGZ1bmN0aW9uIChkZXNjcmlwdGlvbiwgc3VjY2Vzc0NhbGxiYWNrLCBmYWlsdXJlQ2FsbGJhY2spIHtcbiAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgZGVzY3JpcHRpb24gPSBzaW11bGNhc3QudHJhbnNmb3JtUmVtb3RlRGVzY3JpcHRpb24oZGVzY3JpcHRpb24pO1xuICAgIHRoaXMudHJhY2UoJ3NldFJlbW90ZURlc2NyaXB0aW9uJywgZHVtcFNEUChkZXNjcmlwdGlvbikpO1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24uc2V0UmVtb3RlRGVzY3JpcHRpb24oZGVzY3JpcHRpb24sXG4gICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHNlbGYudHJhY2UoJ3NldFJlbW90ZURlc2NyaXB0aW9uT25TdWNjZXNzJyk7XG4gICAgICAgICAgICBzdWNjZXNzQ2FsbGJhY2soKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24gKGVycikge1xuICAgICAgICAgICAgc2VsZi50cmFjZSgnc2V0UmVtb3RlRGVzY3JpcHRpb25PbkZhaWx1cmUnLCBlcnIpO1xuICAgICAgICAgICAgZmFpbHVyZUNhbGxiYWNrKGVycik7XG4gICAgICAgIH1cbiAgICApO1xuICAgIC8qXG4gICAgIGlmICh0aGlzLnN0YXRzaW50ZXJ2YWwgPT09IG51bGwgJiYgdGhpcy5tYXhzdGF0cyA+IDApIHtcbiAgICAgLy8gc3RhcnQgZ2F0aGVyaW5nIHN0YXRzXG4gICAgIH1cbiAgICAgKi9cbn07XG5cblRyYWNlYWJsZVBlZXJDb25uZWN0aW9uLnByb3RvdHlwZS5jbG9zZSA9IGZ1bmN0aW9uICgpIHtcbiAgICB0aGlzLnRyYWNlKCdzdG9wJyk7XG4gICAgaWYgKHRoaXMuc3RhdHNpbnRlcnZhbCAhPT0gbnVsbCkge1xuICAgICAgICB3aW5kb3cuY2xlYXJJbnRlcnZhbCh0aGlzLnN0YXRzaW50ZXJ2YWwpO1xuICAgICAgICB0aGlzLnN0YXRzaW50ZXJ2YWwgPSBudWxsO1xuICAgIH1cbiAgICB0aGlzLnBlZXJjb25uZWN0aW9uLmNsb3NlKCk7XG59O1xuXG5UcmFjZWFibGVQZWVyQ29ubmVjdGlvbi5wcm90b3R5cGUuY3JlYXRlT2ZmZXIgPSBmdW5jdGlvbiAoc3VjY2Vzc0NhbGxiYWNrLCBmYWlsdXJlQ2FsbGJhY2ssIGNvbnN0cmFpbnRzKSB7XG4gICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgIHRoaXMudHJhY2UoJ2NyZWF0ZU9mZmVyJywgSlNPTi5zdHJpbmdpZnkoY29uc3RyYWludHMsIG51bGwsICcgJykpO1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24uY3JlYXRlT2ZmZXIoXG4gICAgICAgIGZ1bmN0aW9uIChvZmZlcikge1xuICAgICAgICAgICAgc2VsZi50cmFjZSgnY3JlYXRlT2ZmZXJPblN1Y2Nlc3MnLCBkdW1wU0RQKG9mZmVyKSk7XG4gICAgICAgICAgICBzdWNjZXNzQ2FsbGJhY2sob2ZmZXIpO1xuICAgICAgICB9LFxuICAgICAgICBmdW5jdGlvbihlcnIpIHtcbiAgICAgICAgICAgIHNlbGYudHJhY2UoJ2NyZWF0ZU9mZmVyT25GYWlsdXJlJywgZXJyKTtcbiAgICAgICAgICAgIGZhaWx1cmVDYWxsYmFjayhlcnIpO1xuICAgICAgICB9LFxuICAgICAgICBjb25zdHJhaW50c1xuICAgICk7XG59O1xuXG5UcmFjZWFibGVQZWVyQ29ubmVjdGlvbi5wcm90b3R5cGUuY3JlYXRlQW5zd2VyID0gZnVuY3Rpb24gKHN1Y2Nlc3NDYWxsYmFjaywgZmFpbHVyZUNhbGxiYWNrLCBjb25zdHJhaW50cykge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB0aGlzLnRyYWNlKCdjcmVhdGVBbnN3ZXInLCBKU09OLnN0cmluZ2lmeShjb25zdHJhaW50cywgbnVsbCwgJyAnKSk7XG4gICAgdGhpcy5wZWVyY29ubmVjdGlvbi5jcmVhdGVBbnN3ZXIoXG4gICAgICAgIGZ1bmN0aW9uIChhbnN3ZXIpIHtcbiAgICAgICAgICAgIGFuc3dlciA9IHNpbXVsY2FzdC50cmFuc2Zvcm1BbnN3ZXIoYW5zd2VyKTtcbiAgICAgICAgICAgIHNlbGYudHJhY2UoJ2NyZWF0ZUFuc3dlck9uU3VjY2VzcycsIGR1bXBTRFAoYW5zd2VyKSk7XG4gICAgICAgICAgICBzdWNjZXNzQ2FsbGJhY2soYW5zd2VyKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24oZXJyKSB7XG4gICAgICAgICAgICBzZWxmLnRyYWNlKCdjcmVhdGVBbnN3ZXJPbkZhaWx1cmUnLCBlcnIpO1xuICAgICAgICAgICAgZmFpbHVyZUNhbGxiYWNrKGVycik7XG4gICAgICAgIH0sXG4gICAgICAgIGNvbnN0cmFpbnRzXG4gICAgKTtcbn07XG5cblRyYWNlYWJsZVBlZXJDb25uZWN0aW9uLnByb3RvdHlwZS5hZGRJY2VDYW5kaWRhdGUgPSBmdW5jdGlvbiAoY2FuZGlkYXRlLCBzdWNjZXNzQ2FsbGJhY2ssIGZhaWx1cmVDYWxsYmFjaykge1xuICAgIHZhciBzZWxmID0gdGhpcztcbiAgICB0aGlzLnRyYWNlKCdhZGRJY2VDYW5kaWRhdGUnLCBKU09OLnN0cmluZ2lmeShjYW5kaWRhdGUsIG51bGwsICcgJykpO1xuICAgIHRoaXMucGVlcmNvbm5lY3Rpb24uYWRkSWNlQ2FuZGlkYXRlKGNhbmRpZGF0ZSk7XG4gICAgLyogbWF5YmUgbGF0ZXJcbiAgICAgdGhpcy5wZWVyY29ubmVjdGlvbi5hZGRJY2VDYW5kaWRhdGUoY2FuZGlkYXRlLFxuICAgICBmdW5jdGlvbiAoKSB7XG4gICAgIHNlbGYudHJhY2UoJ2FkZEljZUNhbmRpZGF0ZU9uU3VjY2VzcycpO1xuICAgICBzdWNjZXNzQ2FsbGJhY2soKTtcbiAgICAgfSxcbiAgICAgZnVuY3Rpb24gKGVycikge1xuICAgICBzZWxmLnRyYWNlKCdhZGRJY2VDYW5kaWRhdGVPbkZhaWx1cmUnLCBlcnIpO1xuICAgICBmYWlsdXJlQ2FsbGJhY2soZXJyKTtcbiAgICAgfVxuICAgICApO1xuICAgICAqL1xufTtcblxuVHJhY2VhYmxlUGVlckNvbm5lY3Rpb24ucHJvdG90eXBlLmdldFN0YXRzID0gZnVuY3Rpb24oY2FsbGJhY2ssIGVycmJhY2spIHtcbiAgICBpZiAobmF2aWdhdG9yLm1vekdldFVzZXJNZWRpYSkge1xuICAgICAgICAvLyBpZ25vcmUgZm9yIG5vdy4uLlxuICAgICAgICBpZighZXJyYmFjaylcbiAgICAgICAgICAgIGVycmJhY2sgPSBmdW5jdGlvbiAoKSB7XG5cbiAgICAgICAgICAgIH1cbiAgICAgICAgdGhpcy5wZWVyY29ubmVjdGlvbi5nZXRTdGF0cyhudWxsLGNhbGxiYWNrLGVycmJhY2spO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMucGVlcmNvbm5lY3Rpb24uZ2V0U3RhdHMoY2FsbGJhY2spO1xuICAgIH1cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gVHJhY2VhYmxlUGVlckNvbm5lY3Rpb247XG5cbiIsIi8qIGdsb2JhbCAkLCAkaXEsIGNvbmZpZywgY29ubmVjdGlvbiwgVUksIG1lc3NhZ2VIYW5kbGVyLFxuIHJvb21OYW1lLCBzZXNzaW9uVGVybWluYXRlZCwgU3Ryb3BoZSwgVXRpbCAqL1xuLyoqXG4gKiBDb250YWlucyBsb2dpYyByZXNwb25zaWJsZSBmb3IgZW5hYmxpbmcvZGlzYWJsaW5nIGZ1bmN0aW9uYWxpdHkgYXZhaWxhYmxlXG4gKiBvbmx5IHRvIG1vZGVyYXRvciB1c2Vycy5cbiAqL1xudmFyIGNvbm5lY3Rpb24gPSBudWxsO1xudmFyIGZvY3VzVXNlckppZDtcbnZhciBnZXROZXh0VGltZW91dCA9IFV0aWwuY3JlYXRlRXhwQmFja29mZlRpbWVyKDEwMDApO1xudmFyIGdldE5leHRFcnJvclRpbWVvdXQgPSBVdGlsLmNyZWF0ZUV4cEJhY2tvZmZUaW1lcigxMDAwKTtcbi8vIEV4dGVybmFsIGF1dGhlbnRpY2F0aW9uIHN0dWZmXG52YXIgZXh0ZXJuYWxBdXRoRW5hYmxlZCA9IGZhbHNlO1xuLy8gU2lwIGdhdGV3YXkgY2FuIGJlIGVuYWJsZWQgYnkgY29uZmlndXJpbmcgSmlnYXNpIGhvc3QgaW4gY29uZmlnLmpzIG9yXG4vLyBpdCB3aWxsIGJlIGVuYWJsZWQgYXV0b21hdGljYWxseSBpZiBmb2N1cyBkZXRlY3RzIHRoZSBjb21wb25lbnQgdGhyb3VnaFxuLy8gc2VydmljZSBkaXNjb3ZlcnkuXG52YXIgc2lwR2F0ZXdheUVuYWJsZWQgPSBjb25maWcuaG9zdHMuY2FsbF9jb250cm9sICE9PSB1bmRlZmluZWQ7XG5cbnZhciBNb2RlcmF0b3IgPSB7XG4gICAgaXNNb2RlcmF0b3I6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIGNvbm5lY3Rpb24gJiYgY29ubmVjdGlvbi5lbXVjLmlzTW9kZXJhdG9yKCk7XG4gICAgfSxcblxuICAgIGlzUGVlck1vZGVyYXRvcjogZnVuY3Rpb24gKHBlZXJKaWQpIHtcbiAgICAgICAgcmV0dXJuIGNvbm5lY3Rpb24gJiZcbiAgICAgICAgICAgIGNvbm5lY3Rpb24uZW11Yy5nZXRNZW1iZXJSb2xlKHBlZXJKaWQpID09PSAnbW9kZXJhdG9yJztcbiAgICB9LFxuXG4gICAgaXNFeHRlcm5hbEF1dGhFbmFibGVkOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBleHRlcm5hbEF1dGhFbmFibGVkO1xuICAgIH0sXG5cbiAgICBpc1NpcEdhdGV3YXlFbmFibGVkOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBzaXBHYXRld2F5RW5hYmxlZDtcbiAgICB9LFxuXG4gICAgc2V0Q29ubmVjdGlvbjogZnVuY3Rpb24gKGNvbikge1xuICAgICAgICBjb25uZWN0aW9uID0gY29uO1xuICAgIH0sXG5cbiAgICBpbml0OiBmdW5jdGlvbiAoeG1wcCkge1xuICAgICAgICB0aGlzLnhtcHBTZXJ2aWNlID0geG1wcDtcbiAgICAgICAgdGhpcy5vbkxvY2FsUm9sZUNoYW5nZSA9IGZ1bmN0aW9uIChmcm9tLCBtZW1iZXIsIHByZXMpIHtcbiAgICAgICAgICAgIFVJLm9uTW9kZXJhdG9yU3RhdHVzQ2hhbmdlZChNb2RlcmF0b3IuaXNNb2RlcmF0b3IoKSk7XG4gICAgICAgIH07XG4gICAgfSxcblxuICAgIG9uTXVjTGVmdDogZnVuY3Rpb24gKGppZCkge1xuICAgICAgICBjb25zb2xlLmluZm8oXCJTb21lb25lIGxlZnQgaXMgaXQgZm9jdXMgPyBcIiArIGppZCk7XG4gICAgICAgIHZhciByZXNvdXJjZSA9IFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGppZCk7XG4gICAgICAgIGlmIChyZXNvdXJjZSA9PT0gJ2ZvY3VzJyAmJiAhdGhpcy54bXBwU2VydmljZS5zZXNzaW9uVGVybWluYXRlZCkge1xuICAgICAgICAgICAgY29uc29sZS5pbmZvKFxuICAgICAgICAgICAgICAgIFwiRm9jdXMgaGFzIGxlZnQgdGhlIHJvb20gLSBsZWF2aW5nIGNvbmZlcmVuY2VcIik7XG4gICAgICAgICAgICAvL2hhbmdVcCgpO1xuICAgICAgICAgICAgLy8gV2UnZCByYXRoZXIgcmVsb2FkIHRvIGhhdmUgZXZlcnl0aGluZyByZS1pbml0aWFsaXplZFxuICAgICAgICAgICAgLy8gRklYTUU6IHNob3cgc29tZSBtZXNzYWdlIGJlZm9yZSByZWxvYWRcbiAgICAgICAgICAgIGxvY2F0aW9uLnJlbG9hZCgpO1xuICAgICAgICB9XG4gICAgfSxcbiAgICBcbiAgICBzZXRGb2N1c1VzZXJKaWQ6IGZ1bmN0aW9uIChmb2N1c0ppZCkge1xuICAgICAgICBpZiAoIWZvY3VzVXNlckppZCkge1xuICAgICAgICAgICAgZm9jdXNVc2VySmlkID0gZm9jdXNKaWQ7XG4gICAgICAgICAgICBjb25zb2xlLmluZm8oXCJGb2N1cyBqaWQgc2V0IHRvOiBcIiArIGZvY3VzVXNlckppZCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgZ2V0Rm9jdXNVc2VySmlkOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBmb2N1c1VzZXJKaWQ7XG4gICAgfSxcblxuICAgIGdldEZvY3VzQ29tcG9uZW50OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIC8vIEdldCBmb2N1cyBjb21wb25lbnQgYWRkcmVzc1xuICAgICAgICB2YXIgZm9jdXNDb21wb25lbnQgPSBjb25maWcuaG9zdHMuZm9jdXM7XG4gICAgICAgIC8vIElmIG5vdCBzcGVjaWZpZWQgdXNlIGRlZmF1bHQ6ICdmb2N1cy5kb21haW4nXG4gICAgICAgIGlmICghZm9jdXNDb21wb25lbnQpIHtcbiAgICAgICAgICAgIGZvY3VzQ29tcG9uZW50ID0gJ2ZvY3VzLicgKyBjb25maWcuaG9zdHMuZG9tYWluO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBmb2N1c0NvbXBvbmVudDtcbiAgICB9LFxuXG4gICAgY3JlYXRlQ29uZmVyZW5jZUlxOiBmdW5jdGlvbiAocm9vbU5hbWUpIHtcbiAgICAgICAgLy8gR2VuZXJhdGUgY3JlYXRlIGNvbmZlcmVuY2UgSVFcbiAgICAgICAgdmFyIGVsZW0gPSAkaXEoe3RvOiBNb2RlcmF0b3IuZ2V0Rm9jdXNDb21wb25lbnQoKSwgdHlwZTogJ3NldCd9KTtcbiAgICAgICAgZWxlbS5jKCdjb25mZXJlbmNlJywge1xuICAgICAgICAgICAgeG1sbnM6ICdodHRwOi8vaml0c2kub3JnL3Byb3RvY29sL2ZvY3VzJyxcbiAgICAgICAgICAgIHJvb206IHJvb21OYW1lXG4gICAgICAgIH0pO1xuICAgICAgICBpZiAoY29uZmlnLmhvc3RzLmJyaWRnZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBlbGVtLmMoXG4gICAgICAgICAgICAgICAgJ3Byb3BlcnR5JyxcbiAgICAgICAgICAgICAgICB7IG5hbWU6ICdicmlkZ2UnLCB2YWx1ZTogY29uZmlnLmhvc3RzLmJyaWRnZX0pXG4gICAgICAgICAgICAgICAgLnVwKCk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gVGVsbCB0aGUgZm9jdXMgd2UgaGF2ZSBKaWdhc2kgY29uZmlndXJlZFxuICAgICAgICBpZiAoY29uZmlnLmhvc3RzLmNhbGxfY29udHJvbCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBlbGVtLmMoXG4gICAgICAgICAgICAgICAgJ3Byb3BlcnR5JyxcbiAgICAgICAgICAgICAgICB7IG5hbWU6ICdjYWxsX2NvbnRyb2wnLCB2YWx1ZTogY29uZmlnLmhvc3RzLmNhbGxfY29udHJvbH0pXG4gICAgICAgICAgICAgICAgLnVwKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNvbmZpZy5jaGFubmVsTGFzdE4gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgZWxlbS5jKFxuICAgICAgICAgICAgICAgICdwcm9wZXJ0eScsXG4gICAgICAgICAgICAgICAgeyBuYW1lOiAnY2hhbm5lbExhc3ROJywgdmFsdWU6IGNvbmZpZy5jaGFubmVsTGFzdE59KVxuICAgICAgICAgICAgICAgIC51cCgpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChjb25maWcuYWRhcHRpdmVMYXN0TiAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBlbGVtLmMoXG4gICAgICAgICAgICAgICAgJ3Byb3BlcnR5JyxcbiAgICAgICAgICAgICAgICB7IG5hbWU6ICdhZGFwdGl2ZUxhc3ROJywgdmFsdWU6IGNvbmZpZy5hZGFwdGl2ZUxhc3ROfSlcbiAgICAgICAgICAgICAgICAudXAoKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoY29uZmlnLmFkYXB0aXZlU2ltdWxjYXN0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIGVsZW0uYyhcbiAgICAgICAgICAgICAgICAncHJvcGVydHknLFxuICAgICAgICAgICAgICAgIHsgbmFtZTogJ2FkYXB0aXZlU2ltdWxjYXN0JywgdmFsdWU6IGNvbmZpZy5hZGFwdGl2ZVNpbXVsY2FzdH0pXG4gICAgICAgICAgICAgICAgLnVwKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNvbmZpZy5vcGVuU2N0cCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBlbGVtLmMoXG4gICAgICAgICAgICAgICAgJ3Byb3BlcnR5JyxcbiAgICAgICAgICAgICAgICB7IG5hbWU6ICdvcGVuU2N0cCcsIHZhbHVlOiBjb25maWcub3BlblNjdHB9KVxuICAgICAgICAgICAgICAgIC51cCgpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChjb25maWcuZW5hYmxlRmlyZWZveFN1cHBvcnQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgZWxlbS5jKFxuICAgICAgICAgICAgICAgICdwcm9wZXJ0eScsXG4gICAgICAgICAgICAgICAgeyBuYW1lOiAnZW5hYmxlRmlyZWZveEhhY2tzJyxcbiAgICAgICAgICAgICAgICAgICAgdmFsdWU6IGNvbmZpZy5lbmFibGVGaXJlZm94U3VwcG9ydH0pXG4gICAgICAgICAgICAgICAgLnVwKCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxlbS51cCgpO1xuICAgICAgICByZXR1cm4gZWxlbTtcbiAgICB9LFxuXG4gICAgcGFyc2VDb25maWdPcHRpb25zOiBmdW5jdGlvbiAocmVzdWx0SXEpIHtcbiAgICBcbiAgICAgICAgTW9kZXJhdG9yLnNldEZvY3VzVXNlckppZChcbiAgICAgICAgICAgICQocmVzdWx0SXEpLmZpbmQoJ2NvbmZlcmVuY2UnKS5hdHRyKCdmb2N1c2ppZCcpKTtcbiAgICBcbiAgICAgICAgdmFyIGV4dEF1dGhQYXJhbVxuICAgICAgICAgICAgPSAkKHJlc3VsdElxKS5maW5kKCc+Y29uZmVyZW5jZT5wcm9wZXJ0eVtuYW1lPVxcJ2V4dGVybmFsQXV0aFxcJ10nKTtcbiAgICAgICAgaWYgKGV4dEF1dGhQYXJhbS5sZW5ndGgpIHtcbiAgICAgICAgICAgIGV4dGVybmFsQXV0aEVuYWJsZWQgPSBleHRBdXRoUGFyYW0uYXR0cigndmFsdWUnKSA9PT0gJ3RydWUnO1xuICAgICAgICB9XG4gICAgXG4gICAgICAgIGNvbnNvbGUuaW5mbyhcIkV4dGVybmFsIGF1dGhlbnRpY2F0aW9uIGVuYWJsZWQ6IFwiICsgZXh0ZXJuYWxBdXRoRW5hYmxlZCk7XG4gICAgXG4gICAgICAgIC8vIENoZWNrIGlmIGZvY3VzIGhhcyBhdXRvLWRldGVjdGVkIEppZ2FzaSBjb21wb25lbnQodGhpcyB3aWxsIGJlIGFsc29cbiAgICAgICAgLy8gaW5jbHVkZWQgaWYgd2UgaGF2ZSBwYXNzZWQgb3VyIGhvc3QgZnJvbSB0aGUgY29uZmlnKVxuICAgICAgICBpZiAoJChyZXN1bHRJcSkuZmluZChcbiAgICAgICAgICAgICc+Y29uZmVyZW5jZT5wcm9wZXJ0eVtuYW1lPVxcJ3NpcEdhdGV3YXlFbmFibGVkXFwnXScpLmxlbmd0aCkge1xuICAgICAgICAgICAgc2lwR2F0ZXdheUVuYWJsZWQgPSB0cnVlO1xuICAgICAgICB9XG4gICAgXG4gICAgICAgIGNvbnNvbGUuaW5mbyhcIlNpcCBnYXRld2F5IGVuYWJsZWQ6IFwiICsgc2lwR2F0ZXdheUVuYWJsZWQpO1xuICAgIH0sXG5cbiAgICAvLyBGSVhNRTogd2UgbmVlZCB0byBzaG93IHRoZSBmYWN0IHRoYXQgd2UncmUgd2FpdGluZyBmb3IgdGhlIGZvY3VzXG4gICAgLy8gdG8gdGhlIHVzZXIob3IgdGhhdCBmb2N1cyBpcyBub3QgYXZhaWxhYmxlKVxuICAgIGFsbG9jYXRlQ29uZmVyZW5jZUZvY3VzOiBmdW5jdGlvbiAocm9vbU5hbWUsIGNhbGxiYWNrKSB7XG4gICAgICAgIC8vIFRyeSB0byB1c2UgZm9jdXMgdXNlciBKSUQgZnJvbSB0aGUgY29uZmlnXG4gICAgICAgIE1vZGVyYXRvci5zZXRGb2N1c1VzZXJKaWQoY29uZmlnLmZvY3VzVXNlckppZCk7XG4gICAgICAgIC8vIFNlbmQgY3JlYXRlIGNvbmZlcmVuY2UgSVFcbiAgICAgICAgdmFyIGlxID0gTW9kZXJhdG9yLmNyZWF0ZUNvbmZlcmVuY2VJcShyb29tTmFtZSk7XG4gICAgICAgIGNvbm5lY3Rpb24uc2VuZElRKFxuICAgICAgICAgICAgaXEsXG4gICAgICAgICAgICBmdW5jdGlvbiAocmVzdWx0KSB7XG4gICAgICAgICAgICAgICAgaWYgKCd0cnVlJyA9PT0gJChyZXN1bHQpLmZpbmQoJ2NvbmZlcmVuY2UnKS5hdHRyKCdyZWFkeScpKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIFJlc2V0IGJvdGggdGltZXJzXG4gICAgICAgICAgICAgICAgICAgIGdldE5leHRUaW1lb3V0KHRydWUpO1xuICAgICAgICAgICAgICAgICAgICBnZXROZXh0RXJyb3JUaW1lb3V0KHRydWUpO1xuICAgICAgICAgICAgICAgICAgICAvLyBTZXR1cCBjb25maWcgb3B0aW9uc1xuICAgICAgICAgICAgICAgICAgICBNb2RlcmF0b3IucGFyc2VDb25maWdPcHRpb25zKHJlc3VsdCk7XG4gICAgICAgICAgICAgICAgICAgIC8vIEV4ZWMgY2FsbGJhY2tcbiAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2soKTtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB2YXIgd2FpdE1zID0gZ2V0TmV4dFRpbWVvdXQoKTtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5pbmZvKFwiV2FpdGluZyBmb3IgdGhlIGZvY3VzLi4uIFwiICsgd2FpdE1zKTtcbiAgICAgICAgICAgICAgICAgICAgLy8gUmVzZXQgZXJyb3IgdGltZW91dFxuICAgICAgICAgICAgICAgICAgICBnZXROZXh0RXJyb3JUaW1lb3V0KHRydWUpO1xuICAgICAgICAgICAgICAgICAgICB3aW5kb3cuc2V0VGltZW91dChcbiAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBNb2RlcmF0b3IuYWxsb2NhdGVDb25mZXJlbmNlRm9jdXMoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvb21OYW1lLCBjYWxsYmFjayk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9LCB3YWl0TXMpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBmdW5jdGlvbiAoZXJyb3IpIHtcbiAgICAgICAgICAgICAgICAvLyBOb3QgYXV0aG9yaXplZCB0byBjcmVhdGUgbmV3IHJvb21cbiAgICAgICAgICAgICAgICBpZiAoJChlcnJvcikuZmluZCgnPmVycm9yPm5vdC1hdXRob3JpemVkJykubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybihcIlVuYXV0aG9yaXplZCB0byBzdGFydCB0aGUgY29uZmVyZW5jZVwiKTtcbiAgICAgICAgICAgICAgICAgICAgVUkub25BdXRoZW50aWNhdGlvblJlcXVpcmVkKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVyYXRvci5hbGxvY2F0ZUNvbmZlcmVuY2VGb2N1cyhyb29tTmFtZSwgY2FsbGJhY2spO1xuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB2YXIgd2FpdE1zID0gZ2V0TmV4dEVycm9yVGltZW91dCgpO1xuICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJGb2N1cyBlcnJvciwgcmV0cnkgYWZ0ZXIgXCIgKyB3YWl0TXMsIGVycm9yKTtcbiAgICAgICAgICAgICAgICAvLyBTaG93IG1lc3NhZ2VcbiAgICAgICAgICAgICAgICBVSS5tZXNzYWdlSGFuZGxlci5ub3RpZnkoXG4gICAgICAgICAgICAgICAgICAgICdDb25mZXJlbmNlIGZvY3VzJywgJ2Rpc2Nvbm5lY3RlZCcsXG4gICAgICAgICAgICAgICAgICAgICAgICBNb2RlcmF0b3IuZ2V0Rm9jdXNDb21wb25lbnQoKSArXG4gICAgICAgICAgICAgICAgICAgICAgICAnIG5vdCBhdmFpbGFibGUgLSByZXRyeSBpbiAnICtcbiAgICAgICAgICAgICAgICAgICAgICAgICh3YWl0TXMgLyAxMDAwKSArICcgc2VjJyk7XG4gICAgICAgICAgICAgICAgLy8gUmVzZXQgcmVzcG9uc2UgdGltZW91dFxuICAgICAgICAgICAgICAgIGdldE5leHRUaW1lb3V0KHRydWUpO1xuICAgICAgICAgICAgICAgIHdpbmRvdy5zZXRUaW1lb3V0KFxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBNb2RlcmF0b3IuYWxsb2NhdGVDb25mZXJlbmNlRm9jdXMocm9vbU5hbWUsIGNhbGxiYWNrKTtcbiAgICAgICAgICAgICAgICAgICAgfSwgd2FpdE1zKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgKTtcbiAgICB9LFxuXG4gICAgZ2V0QXV0aFVybDogZnVuY3Rpb24gKHJvb21OYW1lLCB1cmxDYWxsYmFjaykge1xuICAgICAgICB2YXIgaXEgPSAkaXEoe3RvOiBNb2RlcmF0b3IuZ2V0Rm9jdXNDb21wb25lbnQoKSwgdHlwZTogJ2dldCd9KTtcbiAgICAgICAgaXEuYygnYXV0aC11cmwnLCB7XG4gICAgICAgICAgICB4bWxuczogJ2h0dHA6Ly9qaXRzaS5vcmcvcHJvdG9jb2wvZm9jdXMnLFxuICAgICAgICAgICAgcm9vbTogcm9vbU5hbWVcbiAgICAgICAgfSk7XG4gICAgICAgIGNvbm5lY3Rpb24uc2VuZElRKFxuICAgICAgICAgICAgaXEsXG4gICAgICAgICAgICBmdW5jdGlvbiAocmVzdWx0KSB7XG4gICAgICAgICAgICAgICAgdmFyIHVybCA9ICQocmVzdWx0KS5maW5kKCdhdXRoLXVybCcpLmF0dHIoJ3VybCcpO1xuICAgICAgICAgICAgICAgIGlmICh1cmwpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5pbmZvKFwiR290IGF1dGggdXJsOiBcIiArIHVybCk7XG4gICAgICAgICAgICAgICAgICAgIHVybENhbGxiYWNrKHVybCk7XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5lcnJvcihcbiAgICAgICAgICAgICAgICAgICAgICAgIFwiRmFpbGVkIHRvIGdldCBhdXRoIHVybCBmcm8gbXRoZSBmb2N1c1wiLCByZXN1bHQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBmdW5jdGlvbiAoZXJyb3IpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKFwiR2V0IGF1dGggdXJsIGVycm9yXCIsIGVycm9yKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgKTtcbiAgICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IE1vZGVyYXRvcjtcblxuXG5cbiIsIi8qIGdsb2JhbCAkLCAkaXEsIGNvbmZpZywgY29ubmVjdGlvbiwgZm9jdXNNdWNKaWQsIG1lc3NhZ2VIYW5kbGVyLCBNb2RlcmF0b3IsXG4gICBUb29sYmFyLCBVdGlsICovXG52YXIgTW9kZXJhdG9yID0gcmVxdWlyZShcIi4vbW9kZXJhdG9yXCIpO1xuXG5cbnZhciByZWNvcmRpbmdUb2tlbiA9IG51bGw7XG52YXIgcmVjb3JkaW5nRW5hYmxlZDtcblxuLyoqXG4gKiBXaGV0aGVyIHRvIHVzZSBhIGppcmVjb24gY29tcG9uZW50IGZvciByZWNvcmRpbmcsIG9yIHVzZSB0aGUgdmlkZW9icmlkZ2VcbiAqIHRocm91Z2ggQ09MSUJSSS5cbiAqL1xudmFyIHVzZUppcmVjb24gPSAodHlwZW9mIGNvbmZpZy5ob3N0cy5qaXJlY29uICE9IFwidW5kZWZpbmVkXCIpO1xuXG4vKipcbiAqIFRoZSBJRCBvZiB0aGUgamlyZWNvbiByZWNvcmRpbmcgc2Vzc2lvbi4gSmlyZWNvbiBnZW5lcmF0ZXMgaXQgd2hlbiB3ZVxuICogaW5pdGlhbGx5IHN0YXJ0IHJlY29yZGluZywgYW5kIGl0IG5lZWRzIHRvIGJlIHVzZWQgaW4gc3Vic2VxdWVudCByZXF1ZXN0c1xuICogdG8gamlyZWNvbi5cbiAqL1xudmFyIGppcmVjb25SaWQgPSBudWxsO1xuXG5mdW5jdGlvbiBzZXRSZWNvcmRpbmdUb2tlbih0b2tlbikge1xuICAgIHJlY29yZGluZ1Rva2VuID0gdG9rZW47XG59XG5cbmZ1bmN0aW9uIHNldFJlY29yZGluZyhzdGF0ZSwgdG9rZW4sIGNhbGxiYWNrKSB7XG4gICAgaWYgKHVzZUppcmVjb24pe1xuICAgICAgICB0aGlzLnNldFJlY29yZGluZ0ppcmVjb24oc3RhdGUsIHRva2VuLCBjYWxsYmFjayk7XG4gICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5zZXRSZWNvcmRpbmdDb2xpYnJpKHN0YXRlLCB0b2tlbiwgY2FsbGJhY2spO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gc2V0UmVjb3JkaW5nSmlyZWNvbihzdGF0ZSwgdG9rZW4sIGNhbGxiYWNrKSB7XG4gICAgaWYgKHN0YXRlID09IHJlY29yZGluZ0VuYWJsZWQpe1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdmFyIGlxID0gJGlxKHt0bzogY29uZmlnLmhvc3RzLmppcmVjb24sIHR5cGU6ICdzZXQnfSlcbiAgICAgICAgLmMoJ3JlY29yZGluZycsIHt4bWxuczogJ2h0dHA6Ly9qaXRzaS5vcmcvcHJvdG9jb2wvamlyZWNvbicsXG4gICAgICAgICAgICBhY3Rpb246IHN0YXRlID8gJ3N0YXJ0JyA6ICdzdG9wJyxcbiAgICAgICAgICAgIG11Y2ppZDogY29ubmVjdGlvbi5lbXVjLnJvb21qaWR9KTtcbiAgICBpZiAoIXN0YXRlKXtcbiAgICAgICAgaXEuYXR0cnMoe3JpZDogamlyZWNvblJpZH0pO1xuICAgIH1cblxuICAgIGNvbnNvbGUubG9nKCdTdGFydCByZWNvcmRpbmcnKTtcblxuICAgIGNvbm5lY3Rpb24uc2VuZElRKFxuICAgICAgICBpcSxcbiAgICAgICAgZnVuY3Rpb24gKHJlc3VsdCkge1xuICAgICAgICAgICAgLy8gVE9ETyB3YWl0IGZvciBhbiBJUSB3aXRoIHRoZSByZWFsIHN0YXR1cywgc2luY2UgdGhpcyBpc1xuICAgICAgICAgICAgLy8gcHJvdmlzaW9uYWw/XG4gICAgICAgICAgICBqaXJlY29uUmlkID0gJChyZXN1bHQpLmZpbmQoJ3JlY29yZGluZycpLmF0dHIoJ3JpZCcpO1xuICAgICAgICAgICAgY29uc29sZS5sb2coJ1JlY29yZGluZyAnICsgKHN0YXRlID8gJ3N0YXJ0ZWQnIDogJ3N0b3BwZWQnKSArXG4gICAgICAgICAgICAgICAgJyhqaXJlY29uKScgKyByZXN1bHQpO1xuICAgICAgICAgICAgcmVjb3JkaW5nRW5hYmxlZCA9IHN0YXRlO1xuICAgICAgICAgICAgaWYgKCFzdGF0ZSl7XG4gICAgICAgICAgICAgICAgamlyZWNvblJpZCA9IG51bGw7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNhbGxiYWNrKHN0YXRlKTtcbiAgICAgICAgfSxcbiAgICAgICAgZnVuY3Rpb24gKGVycm9yKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZygnRmFpbGVkIHRvIHN0YXJ0IHJlY29yZGluZywgZXJyb3I6ICcsIGVycm9yKTtcbiAgICAgICAgICAgIGNhbGxiYWNrKHJlY29yZGluZ0VuYWJsZWQpO1xuICAgICAgICB9KTtcbn1cblxuLy8gU2VuZHMgYSBDT0xJQlJJIG1lc3NhZ2Ugd2hpY2ggZW5hYmxlcyBvciBkaXNhYmxlcyAoYWNjb3JkaW5nIHRvICdzdGF0ZScpXG4vLyB0aGUgcmVjb3JkaW5nIG9uIHRoZSBicmlkZ2UuIFdhaXRzIGZvciB0aGUgcmVzdWx0IElRIGFuZCBjYWxscyAnY2FsbGJhY2snXG4vLyB3aXRoIHRoZSBuZXcgcmVjb3JkaW5nIHN0YXRlLCBhY2NvcmRpbmcgdG8gdGhlIElRLlxuZnVuY3Rpb24gc2V0UmVjb3JkaW5nQ29saWJyaShzdGF0ZSwgdG9rZW4sIGNhbGxiYWNrKSB7XG4gICAgdmFyIGVsZW0gPSAkaXEoe3RvOiBmb2N1c011Y0ppZCwgdHlwZTogJ3NldCd9KTtcbiAgICBlbGVtLmMoJ2NvbmZlcmVuY2UnLCB7XG4gICAgICAgIHhtbG5zOiAnaHR0cDovL2ppdHNpLm9yZy9wcm90b2NvbC9jb2xpYnJpJ1xuICAgIH0pO1xuICAgIGVsZW0uYygncmVjb3JkaW5nJywge3N0YXRlOiBzdGF0ZSwgdG9rZW46IHRva2VufSk7XG5cbiAgICBjb25uZWN0aW9uLnNlbmRJUShlbGVtLFxuICAgICAgICBmdW5jdGlvbiAocmVzdWx0KSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZygnU2V0IHJlY29yZGluZyBcIicsIHN0YXRlLCAnXCIuIFJlc3VsdDonLCByZXN1bHQpO1xuICAgICAgICAgICAgdmFyIHJlY29yZGluZ0VsZW0gPSAkKHJlc3VsdCkuZmluZCgnPmNvbmZlcmVuY2U+cmVjb3JkaW5nJyk7XG4gICAgICAgICAgICB2YXIgbmV3U3RhdGUgPSAoJ3RydWUnID09PSByZWNvcmRpbmdFbGVtLmF0dHIoJ3N0YXRlJykpO1xuXG4gICAgICAgICAgICByZWNvcmRpbmdFbmFibGVkID0gbmV3U3RhdGU7XG4gICAgICAgICAgICBjYWxsYmFjayhuZXdTdGF0ZSk7XG4gICAgICAgIH0sXG4gICAgICAgIGZ1bmN0aW9uIChlcnJvcikge1xuICAgICAgICAgICAgY29uc29sZS53YXJuKGVycm9yKTtcbiAgICAgICAgICAgIGNhbGxiYWNrKHJlY29yZGluZ0VuYWJsZWQpO1xuICAgICAgICB9XG4gICAgKTtcbn1cblxudmFyIFJlY29yZGluZyA9IHtcbiAgICB0b2dnbGVSZWNvcmRpbmc6IGZ1bmN0aW9uICh0b2tlbkVtcHR5Q2FsbGJhY2ssXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRpbmdDYWxsYmFjaywgc3RhcnRlZENhbGxiYWNrKSB7XG4gICAgICAgIGlmICghTW9kZXJhdG9yLmlzTW9kZXJhdG9yKCkpIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKFxuICAgICAgICAgICAgICAgICAgICAnbm9uLWZvY3VzLCBvciBjb25mZXJlbmNlIG5vdCB5ZXQgb3JnYW5pemVkOicgK1xuICAgICAgICAgICAgICAgICAgICAnIG5vdCBlbmFibGluZyByZWNvcmRpbmcnKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIEppcmVjb24gZG9lcyBub3QgKGN1cnJlbnRseSkgc3VwcG9ydCBhIHRva2VuLlxuICAgICAgICBpZiAoIXJlY29yZGluZ1Rva2VuICYmICF1c2VKaXJlY29uKSB7XG4gICAgICAgICAgICB0b2tlbkVtcHR5Q2FsbGJhY2soZnVuY3Rpb24gKHZhbHVlKSB7XG4gICAgICAgICAgICAgICAgc2V0UmVjb3JkaW5nVG9rZW4odmFsdWUpO1xuICAgICAgICAgICAgICAgIHRoaXMudG9nZ2xlUmVjb3JkaW5nKCk7XG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIG9sZFN0YXRlID0gcmVjb3JkaW5nRW5hYmxlZDtcbiAgICAgICAgc3RhcnRpbmdDYWxsYmFjayghb2xkU3RhdGUpO1xuICAgICAgICBzZXRSZWNvcmRpbmcoIW9sZFN0YXRlLFxuICAgICAgICAgICAgcmVjb3JkaW5nVG9rZW4sXG4gICAgICAgICAgICBmdW5jdGlvbiAoc3RhdGUpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmxvZyhcIk5ldyByZWNvcmRpbmcgc3RhdGU6IFwiLCBzdGF0ZSk7XG4gICAgICAgICAgICAgICAgaWYgKHN0YXRlID09PSBvbGRTdGF0ZSkge1xuICAgICAgICAgICAgICAgICAgICAvLyBGSVhNRTogbmV3IGZvY3VzOlxuICAgICAgICAgICAgICAgICAgICAvLyB0aGlzIHdpbGwgbm90IHdvcmsgd2hlbiBtb2RlcmF0b3IgY2hhbmdlc1xuICAgICAgICAgICAgICAgICAgICAvLyBkdXJpbmcgYWN0aXZlIHNlc3Npb24uIFRoZW4gaXQgd2lsbCBhc3N1bWUgdGhhdFxuICAgICAgICAgICAgICAgICAgICAvLyByZWNvcmRpbmcgc3RhdHVzIGhhcyBjaGFuZ2VkIHRvIHRydWUsIGJ1dCBpdCBtaWdodCBoYXZlXG4gICAgICAgICAgICAgICAgICAgIC8vIGJlZW4gYWxyZWFkeSB0cnVlKGFuZCB3ZSBvbmx5IHJlY2VpdmVkIGFjdHVhbCBzdGF0dXMgZnJvbVxuICAgICAgICAgICAgICAgICAgICAvLyB0aGUgZm9jdXMpLlxuICAgICAgICAgICAgICAgICAgICAvL1xuICAgICAgICAgICAgICAgICAgICAvLyBTTyB3ZSBzdGFydCB3aXRoIHN0YXR1cyBudWxsLCBzbyB0aGF0IGl0IGlzIGluaXRpYWxpemVkXG4gICAgICAgICAgICAgICAgICAgIC8vIGhlcmUgYW5kIHdpbGwgZmFpbCBvbmx5IGFmdGVyIHNlY29uZCBjbGljaywgc28gaWYgaW52YWxpZFxuICAgICAgICAgICAgICAgICAgICAvLyB0b2tlbiB3YXMgdXNlZCB3ZSBoYXZlIHRvIHByZXNzIHRoZSBidXR0b24gdHdpY2UgYmVmb3JlXG4gICAgICAgICAgICAgICAgICAgIC8vIGN1cnJlbnQgc3RhdHVzIHdpbGwgYmUgZmV0Y2hlZCBhbmQgdG9rZW4gd2lsbCBiZSByZXNldC5cbiAgICAgICAgICAgICAgICAgICAgLy9cbiAgICAgICAgICAgICAgICAgICAgLy8gUmVsaWFibGUgd2F5IHdvdWxkIGJlIHRvIHJldHVybiBhdXRoZW50aWNhdGlvbiBlcnJvci5cbiAgICAgICAgICAgICAgICAgICAgLy8gT3Igc3RhdHVzIHVwZGF0ZSB3aGVuIG1vZGVyYXRvciBjb25uZWN0cy5cbiAgICAgICAgICAgICAgICAgICAgLy8gT3Igd2UgaGF2ZSB0byBzdG9wIHJlY29yZGluZyBzZXNzaW9uIHdoZW4gY3VycmVudFxuICAgICAgICAgICAgICAgICAgICAvLyBtb2RlcmF0b3IgbGVhdmVzIHRoZSByb29tLlxuXG4gICAgICAgICAgICAgICAgICAgIC8vIEZhaWxlZCB0byBjaGFuZ2UsIHJlc2V0IHRoZSB0b2tlbiBiZWNhdXNlIGl0IG1pZ2h0XG4gICAgICAgICAgICAgICAgICAgIC8vIGhhdmUgYmVlbiB3cm9uZ1xuICAgICAgICAgICAgICAgICAgICBzZXRSZWNvcmRpbmdUb2tlbihudWxsKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgc3RhcnRlZENhbGxiYWNrKHN0YXRlKTtcblxuICAgICAgICAgICAgfVxuICAgICAgICApO1xuICAgIH1cblxufVxuXG5tb2R1bGUuZXhwb3J0cyA9IFJlY29yZGluZzsiLCIvKiBqc2hpbnQgLVcxMTcgKi9cbi8qIGEgc2ltcGxlIE1VQyBjb25uZWN0aW9uIHBsdWdpblxuICogY2FuIG9ubHkgaGFuZGxlIGEgc2luZ2xlIE1VQyByb29tXG4gKi9cblxudmFyIGJyaWRnZUlzRG93biA9IGZhbHNlO1xuXG52YXIgTW9kZXJhdG9yID0gcmVxdWlyZShcIi4vbW9kZXJhdG9yXCIpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uKFhNUFAsIGV2ZW50RW1pdHRlcikge1xuICAgIFN0cm9waGUuYWRkQ29ubmVjdGlvblBsdWdpbignZW11YycsIHtcbiAgICAgICAgY29ubmVjdGlvbjogbnVsbCxcbiAgICAgICAgcm9vbWppZDogbnVsbCxcbiAgICAgICAgbXlyb29tamlkOiBudWxsLFxuICAgICAgICBtZW1iZXJzOiB7fSxcbiAgICAgICAgbGlzdF9tZW1iZXJzOiBbXSwgLy8gc28gd2UgY2FuIGVsZWN0IGEgbmV3IGZvY3VzXG4gICAgICAgIHByZXNNYXA6IHt9LFxuICAgICAgICBwcmV6aU1hcDoge30sXG4gICAgICAgIGpvaW5lZDogZmFsc2UsXG4gICAgICAgIGlzT3duZXI6IGZhbHNlLFxuICAgICAgICByb2xlOiBudWxsLFxuICAgICAgICBpbml0OiBmdW5jdGlvbiAoY29ubikge1xuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uID0gY29ubjtcbiAgICAgICAgfSxcbiAgICAgICAgaW5pdFByZXNlbmNlTWFwOiBmdW5jdGlvbiAobXlyb29tamlkKSB7XG4gICAgICAgICAgICB0aGlzLnByZXNNYXBbJ3RvJ10gPSBteXJvb21qaWQ7XG4gICAgICAgICAgICB0aGlzLnByZXNNYXBbJ3hucyddID0gJ2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL211Yyc7XG4gICAgICAgIH0sXG4gICAgICAgIGRvSm9pbjogZnVuY3Rpb24gKGppZCwgcGFzc3dvcmQpIHtcbiAgICAgICAgICAgIHRoaXMubXlyb29tamlkID0gamlkO1xuXG4gICAgICAgICAgICBjb25zb2xlLmluZm8oXCJKb2luZWQgTVVDIGFzIFwiICsgdGhpcy5teXJvb21qaWQpO1xuXG4gICAgICAgICAgICB0aGlzLmluaXRQcmVzZW5jZU1hcCh0aGlzLm15cm9vbWppZCk7XG5cbiAgICAgICAgICAgIGlmICghdGhpcy5yb29tamlkKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5yb29tamlkID0gU3Ryb3BoZS5nZXRCYXJlSmlkRnJvbUppZChqaWQpO1xuICAgICAgICAgICAgICAgIC8vIGFkZCBoYW5kbGVycyAoanVzdCBvbmNlKVxuICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5hZGRIYW5kbGVyKHRoaXMub25QcmVzZW5jZS5iaW5kKHRoaXMpLCBudWxsLCAncHJlc2VuY2UnLCBudWxsLCBudWxsLCB0aGlzLnJvb21qaWQsIHttYXRjaEJhcmU6IHRydWV9KTtcbiAgICAgICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uYWRkSGFuZGxlcih0aGlzLm9uUHJlc2VuY2VVbmF2YWlsYWJsZS5iaW5kKHRoaXMpLCBudWxsLCAncHJlc2VuY2UnLCAndW5hdmFpbGFibGUnLCBudWxsLCB0aGlzLnJvb21qaWQsIHttYXRjaEJhcmU6IHRydWV9KTtcbiAgICAgICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uYWRkSGFuZGxlcih0aGlzLm9uUHJlc2VuY2VFcnJvci5iaW5kKHRoaXMpLCBudWxsLCAncHJlc2VuY2UnLCAnZXJyb3InLCBudWxsLCB0aGlzLnJvb21qaWQsIHttYXRjaEJhcmU6IHRydWV9KTtcbiAgICAgICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uYWRkSGFuZGxlcih0aGlzLm9uTWVzc2FnZS5iaW5kKHRoaXMpLCBudWxsLCAnbWVzc2FnZScsIG51bGwsIG51bGwsIHRoaXMucm9vbWppZCwge21hdGNoQmFyZTogdHJ1ZX0pO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKHBhc3N3b3JkICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnByZXNNYXBbJ3Bhc3N3b3JkJ10gPSBwYXNzd29yZDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRoaXMuc2VuZFByZXNlbmNlKCk7XG4gICAgICAgIH0sXG4gICAgICAgIGRvTGVhdmU6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKFwiZG8gbGVhdmVcIiwgdGhpcy5teXJvb21qaWQpO1xuICAgICAgICAgICAgdmFyIHByZXMgPSAkcHJlcyh7dG86IHRoaXMubXlyb29tamlkLCB0eXBlOiAndW5hdmFpbGFibGUnIH0pO1xuICAgICAgICAgICAgdGhpcy5wcmVzTWFwLmxlbmd0aCA9IDA7XG4gICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uc2VuZChwcmVzKTtcbiAgICAgICAgfSxcbiAgICAgICAgY3JlYXRlTm9uQW5vbnltb3VzUm9vbTogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgLy8gaHR0cDovL3htcHAub3JnL2V4dGVuc2lvbnMveGVwLTAwNDUuaHRtbCNjcmVhdGVyb29tLXJlc2VydmVkXG5cbiAgICAgICAgICAgIHZhciBnZXRGb3JtID0gJGlxKHt0eXBlOiAnZ2V0JywgdG86IHRoaXMucm9vbWppZH0pXG4gICAgICAgICAgICAgICAgLmMoJ3F1ZXJ5Jywge3htbG5zOiAnaHR0cDovL2phYmJlci5vcmcvcHJvdG9jb2wvbXVjI293bmVyJ30pXG4gICAgICAgICAgICAgICAgLmMoJ3gnLCB7eG1sbnM6ICdqYWJiZXI6eDpkYXRhJywgdHlwZTogJ3N1Ym1pdCd9KTtcblxuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLnNlbmRJUShnZXRGb3JtLCBmdW5jdGlvbiAoZm9ybSkge1xuXG4gICAgICAgICAgICAgICAgaWYgKCEkKGZvcm0pLmZpbmQoXG4gICAgICAgICAgICAgICAgICAgICAgICAnPnF1ZXJ5PnhbeG1sbnM9XCJqYWJiZXI6eDpkYXRhXCJdJyArXG4gICAgICAgICAgICAgICAgICAgICAgICAnPmZpZWxkW3Zhcj1cIm11YyNyb29tY29uZmlnX3dob2lzXCJdJykubGVuZ3RoKSB7XG5cbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5lcnJvcignbm9uLWFub255bW91cyByb29tcyBub3Qgc3VwcG9ydGVkJyk7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICB2YXIgZm9ybVN1Ym1pdCA9ICRpcSh7dG86IHRoaXMucm9vbWppZCwgdHlwZTogJ3NldCd9KVxuICAgICAgICAgICAgICAgICAgICAuYygncXVlcnknLCB7eG1sbnM6ICdodHRwOi8vamFiYmVyLm9yZy9wcm90b2NvbC9tdWMjb3duZXInfSk7XG5cbiAgICAgICAgICAgICAgICBmb3JtU3VibWl0LmMoJ3gnLCB7eG1sbnM6ICdqYWJiZXI6eDpkYXRhJywgdHlwZTogJ3N1Ym1pdCd9KTtcblxuICAgICAgICAgICAgICAgIGZvcm1TdWJtaXQuYygnZmllbGQnLCB7J3Zhcic6ICdGT1JNX1RZUEUnfSlcbiAgICAgICAgICAgICAgICAgICAgLmMoJ3ZhbHVlJylcbiAgICAgICAgICAgICAgICAgICAgLnQoJ2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL211YyNyb29tY29uZmlnJykudXAoKS51cCgpO1xuXG4gICAgICAgICAgICAgICAgZm9ybVN1Ym1pdC5jKCdmaWVsZCcsIHsndmFyJzogJ211YyNyb29tY29uZmlnX3dob2lzJ30pXG4gICAgICAgICAgICAgICAgICAgIC5jKCd2YWx1ZScpLnQoJ2FueW9uZScpLnVwKCkudXAoKTtcblxuICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kSVEoZm9ybVN1Ym1pdCk7XG5cbiAgICAgICAgICAgIH0sIGZ1bmN0aW9uIChlcnJvcikge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJFcnJvciBnZXR0aW5nIHJvb20gY29uZmlndXJhdGlvbiBmb3JtXCIpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH0sXG4gICAgICAgIG9uUHJlc2VuY2U6IGZ1bmN0aW9uIChwcmVzKSB7XG4gICAgICAgICAgICB2YXIgZnJvbSA9IHByZXMuZ2V0QXR0cmlidXRlKCdmcm9tJyk7XG5cbiAgICAgICAgICAgIC8vIFdoYXQgaXMgdGhpcyBmb3I/IEEgd29ya2Fyb3VuZCBmb3Igc29tZXRoaW5nP1xuICAgICAgICAgICAgaWYgKHByZXMuZ2V0QXR0cmlidXRlKCd0eXBlJykpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gUGFyc2UgZXRoZXJwYWQgdGFnLlxuICAgICAgICAgICAgdmFyIGV0aGVycGFkID0gJChwcmVzKS5maW5kKCc+ZXRoZXJwYWQnKTtcbiAgICAgICAgICAgIGlmIChldGhlcnBhZC5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICBpZiAoY29uZmlnLmV0aGVycGFkX2Jhc2UgJiYgIU1vZGVyYXRvci5pc01vZGVyYXRvcigpKSB7XG4gICAgICAgICAgICAgICAgICAgIFVJLmluaXRFdGhlcnBhZChldGhlcnBhZC50ZXh0KCkpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gUGFyc2UgcHJlemkgdGFnLlxuICAgICAgICAgICAgdmFyIHByZXNlbnRhdGlvbiA9ICQocHJlcykuZmluZCgnPnByZXppJyk7XG4gICAgICAgICAgICBpZiAocHJlc2VudGF0aW9uLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHZhciB1cmwgPSBwcmVzZW50YXRpb24uYXR0cigndXJsJyk7XG4gICAgICAgICAgICAgICAgdmFyIGN1cnJlbnQgPSBwcmVzZW50YXRpb24uZmluZCgnPmN1cnJlbnQnKS50ZXh0KCk7XG5cbiAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygncHJlc2VudGF0aW9uIGluZm8gcmVjZWl2ZWQgZnJvbScsIGZyb20sIHVybCk7XG5cbiAgICAgICAgICAgICAgICBpZiAodGhpcy5wcmV6aU1hcFtmcm9tXSA9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMucHJlemlNYXBbZnJvbV0gPSB1cmw7XG5cbiAgICAgICAgICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcigncHJlc2VudGF0aW9uYWRkZWQubXVjJywgW2Zyb20sIHVybCwgY3VycmVudF0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcignZ290b3NsaWRlLm11YycsIFtmcm9tLCB1cmwsIGN1cnJlbnRdKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmICh0aGlzLnByZXppTWFwW2Zyb21dICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgICB2YXIgdXJsID0gdGhpcy5wcmV6aU1hcFtmcm9tXTtcbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5wcmV6aU1hcFtmcm9tXTtcbiAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdwcmVzZW50YXRpb25yZW1vdmVkLm11YycsIFtmcm9tLCB1cmxdKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gUGFyc2UgYXVkaW8gaW5mbyB0YWcuXG4gICAgICAgICAgICB2YXIgYXVkaW9NdXRlZCA9ICQocHJlcykuZmluZCgnPmF1ZGlvbXV0ZWQnKTtcbiAgICAgICAgICAgIGlmIChhdWRpb011dGVkLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICQoZG9jdW1lbnQpLnRyaWdnZXIoJ2F1ZGlvbXV0ZWQubXVjJywgW2Zyb20sIGF1ZGlvTXV0ZWQudGV4dCgpXSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIFBhcnNlIHZpZGVvIGluZm8gdGFnLlxuICAgICAgICAgICAgdmFyIHZpZGVvTXV0ZWQgPSAkKHByZXMpLmZpbmQoJz52aWRlb211dGVkJyk7XG4gICAgICAgICAgICBpZiAodmlkZW9NdXRlZC5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCd2aWRlb211dGVkLm11YycsIFtmcm9tLCB2aWRlb011dGVkLnRleHQoKV0pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgc3RhdHMgPSAkKHByZXMpLmZpbmQoJz5zdGF0cycpO1xuICAgICAgICAgICAgaWYgKHN0YXRzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHZhciBzdGF0c09iaiA9IHt9O1xuICAgICAgICAgICAgICAgIFN0cm9waGUuZm9yRWFjaENoaWxkKHN0YXRzWzBdLCBcInN0YXRcIiwgZnVuY3Rpb24gKGVsKSB7XG4gICAgICAgICAgICAgICAgICAgIHN0YXRzT2JqW2VsLmdldEF0dHJpYnV0ZShcIm5hbWVcIildID0gZWwuZ2V0QXR0cmlidXRlKFwidmFsdWVcIik7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgICAgY29ubmVjdGlvbnF1YWxpdHkudXBkYXRlUmVtb3RlU3RhdHMoZnJvbSwgc3RhdHNPYmopO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBQYXJzZSBzdGF0dXMuXG4gICAgICAgICAgICBpZiAoJChwcmVzKS5maW5kKCc+eFt4bWxucz1cImh0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL211YyN1c2VyXCJdPnN0YXR1c1tjb2RlPVwiMjAxXCJdJykubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5pc093bmVyID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB0aGlzLmNyZWF0ZU5vbkFub255bW91c1Jvb20oKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gUGFyc2Ugcm9sZXMuXG4gICAgICAgICAgICB2YXIgbWVtYmVyID0ge307XG4gICAgICAgICAgICBtZW1iZXIuc2hvdyA9ICQocHJlcykuZmluZCgnPnNob3cnKS50ZXh0KCk7XG4gICAgICAgICAgICBtZW1iZXIuc3RhdHVzID0gJChwcmVzKS5maW5kKCc+c3RhdHVzJykudGV4dCgpO1xuICAgICAgICAgICAgdmFyIHRtcCA9ICQocHJlcykuZmluZCgnPnhbeG1sbnM9XCJodHRwOi8vamFiYmVyLm9yZy9wcm90b2NvbC9tdWMjdXNlclwiXT5pdGVtJyk7XG4gICAgICAgICAgICBtZW1iZXIuYWZmaWxpYXRpb24gPSB0bXAuYXR0cignYWZmaWxpYXRpb24nKTtcbiAgICAgICAgICAgIG1lbWJlci5yb2xlID0gdG1wLmF0dHIoJ3JvbGUnKTtcblxuICAgICAgICAgICAgLy8gRm9jdXMgcmVjb2duaXRpb25cbiAgICAgICAgICAgIG1lbWJlci5qaWQgPSB0bXAuYXR0cignamlkJyk7XG4gICAgICAgICAgICBtZW1iZXIuaXNGb2N1cyA9IGZhbHNlO1xuICAgICAgICAgICAgaWYgKG1lbWJlci5qaWRcbiAgICAgICAgICAgICAgICAmJiBtZW1iZXIuamlkLmluZGV4T2YoTW9kZXJhdG9yLmdldEZvY3VzVXNlckppZCgpICsgXCIvXCIpID09IDApIHtcbiAgICAgICAgICAgICAgICBtZW1iZXIuaXNGb2N1cyA9IHRydWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHZhciBuaWNrdGFnID0gJChwcmVzKS5maW5kKCc+bmlja1t4bWxucz1cImh0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL25pY2tcIl0nKTtcbiAgICAgICAgICAgIG1lbWJlci5kaXNwbGF5TmFtZSA9IChuaWNrdGFnLmxlbmd0aCA+IDAgPyBuaWNrdGFnLmh0bWwoKSA6IG51bGwpO1xuXG4gICAgICAgICAgICBpZiAoZnJvbSA9PSB0aGlzLm15cm9vbWppZCkge1xuICAgICAgICAgICAgICAgIGlmIChtZW1iZXIuYWZmaWxpYXRpb24gPT0gJ293bmVyJykgdGhpcy5pc093bmVyID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICBpZiAodGhpcy5yb2xlICE9PSBtZW1iZXIucm9sZSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnJvbGUgPSBtZW1iZXIucm9sZTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKE1vZGVyYXRvci5vbkxvY2FsUm9sZUNoYW5nZSlcbiAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVyYXRvci5vbkxvY2FsUm9sZUNoYW5nZShmcm9tLCBtZW1iZXIsIHByZXMpO1xuICAgICAgICAgICAgICAgICAgICBVSS5vbkxvY2FsUm9sZUNoYW5nZShmcm9tLCBtZW1iZXIsIHByZXMpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAoIXRoaXMuam9pbmVkKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuam9pbmVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRFbWl0dGVyLmVtaXQoWE1QUEV2ZW50cy5NVUNfSk9JTkVELCBmcm9tLCBtZW1iZXIpO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmxpc3RfbWVtYmVycy5wdXNoKGZyb20pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gZWxzZSBpZiAodGhpcy5tZW1iZXJzW2Zyb21dID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICAvLyBuZXcgcGFydGljaXBhbnRcbiAgICAgICAgICAgICAgICB0aGlzLm1lbWJlcnNbZnJvbV0gPSBtZW1iZXI7XG4gICAgICAgICAgICAgICAgdGhpcy5saXN0X21lbWJlcnMucHVzaChmcm9tKTtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygnZW50ZXJlZCcsIGZyb20sIG1lbWJlcik7XG4gICAgICAgICAgICAgICAgaWYgKG1lbWJlci5pc0ZvY3VzKSB7XG4gICAgICAgICAgICAgICAgICAgIGZvY3VzTXVjSmlkID0gZnJvbTtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5pbmZvKFwiSWdub3JlIGZvY3VzOiBcIiArIGZyb20gKyBcIiwgcmVhbCBKSUQ6IFwiICsgbWVtYmVyLmppZCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB2YXIgaWQgPSAkKHByZXMpLmZpbmQoJz51c2VySUQnKS50ZXh0KCk7XG4gICAgICAgICAgICAgICAgICAgIHZhciBlbWFpbCA9ICQocHJlcykuZmluZCgnPmVtYWlsJyk7XG4gICAgICAgICAgICAgICAgICAgIGlmIChlbWFpbC5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZCA9IGVtYWlsLnRleHQoKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBVSS5vbk11Y0VudGVyZWQoZnJvbSwgaWQsIG1lbWJlci5kaXNwbGF5TmFtZSk7XG4gICAgICAgICAgICAgICAgICAgIEFQSS50cmlnZ2VyRXZlbnQoXCJwYXJ0aWNpcGFudEpvaW5lZFwiLCB7amlkOiBmcm9tfSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAvLyBQcmVzZW5jZSB1cGRhdGUgZm9yIGV4aXN0aW5nIHBhcnRpY2lwYW50XG4gICAgICAgICAgICAgICAgLy8gV2F0Y2ggcm9sZSBjaGFuZ2U6XG4gICAgICAgICAgICAgICAgaWYgKHRoaXMubWVtYmVyc1tmcm9tXS5yb2xlICE9IG1lbWJlci5yb2xlKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMubWVtYmVyc1tmcm9tXS5yb2xlID0gbWVtYmVyLnJvbGU7XG4gICAgICAgICAgICAgICAgICAgIFVJLm9uTXVjUm9sZUNoYW5nZWQobWVtYmVyLnJvbGUsIG1lbWJlci5kaXNwbGF5TmFtZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBBbHdheXMgdHJpZ2dlciBwcmVzZW5jZSB0byB1cGRhdGUgYmluZGluZ3NcbiAgICAgICAgICAgICQoZG9jdW1lbnQpLnRyaWdnZXIoJ3ByZXNlbmNlLm11YycsIFtmcm9tLCBtZW1iZXIsIHByZXNdKTtcbiAgICAgICAgICAgIHRoaXMucGFyc2VQcmVzZW5jZShmcm9tLCBtZW1iZXIsIHByZXMpO1xuXG4gICAgICAgICAgICAvLyBUcmlnZ2VyIHN0YXR1cyBtZXNzYWdlIHVwZGF0ZVxuICAgICAgICAgICAgaWYgKG1lbWJlci5zdGF0dXMpIHtcbiAgICAgICAgICAgICAgICBVSS5vbk11Y1ByZXNlbmNlU3RhdHVzKGZyb20sIG1lbWJlcik7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9LFxuICAgICAgICBvblByZXNlbmNlVW5hdmFpbGFibGU6IGZ1bmN0aW9uIChwcmVzKSB7XG4gICAgICAgICAgICB2YXIgZnJvbSA9IHByZXMuZ2V0QXR0cmlidXRlKCdmcm9tJyk7XG4gICAgICAgICAgICAvLyBTdGF0dXMgY29kZSAxMTAgaW5kaWNhdGVzIHRoYXQgdGhpcyBub3RpZmljYXRpb24gaXMgXCJzZWxmLXByZXNlbmNlXCIuXG4gICAgICAgICAgICBpZiAoISQocHJlcykuZmluZCgnPnhbeG1sbnM9XCJodHRwOi8vamFiYmVyLm9yZy9wcm90b2NvbC9tdWMjdXNlclwiXT5zdGF0dXNbY29kZT1cIjExMFwiXScpLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIGRlbGV0ZSB0aGlzLm1lbWJlcnNbZnJvbV07XG4gICAgICAgICAgICAgICAgdGhpcy5saXN0X21lbWJlcnMuc3BsaWNlKHRoaXMubGlzdF9tZW1iZXJzLmluZGV4T2YoZnJvbSksIDEpO1xuICAgICAgICAgICAgICAgIHRoaXMub25QYXJ0aWNpcGFudExlZnQoZnJvbSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBJZiB0aGUgc3RhdHVzIGNvZGUgaXMgMTEwIHRoaXMgbWVhbnMgd2UncmUgbGVhdmluZyBhbmQgd2Ugd291bGQgbGlrZVxuICAgICAgICAgICAgLy8gdG8gcmVtb3ZlIGV2ZXJ5b25lIGVsc2UgZnJvbSBvdXIgdmlldywgc28gd2UgdHJpZ2dlciB0aGUgZXZlbnQuXG4gICAgICAgICAgICBlbHNlIGlmICh0aGlzLmxpc3RfbWVtYmVycy5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLmxpc3RfbWVtYmVycy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgICAgICB2YXIgbWVtYmVyID0gdGhpcy5saXN0X21lbWJlcnNbaV07XG4gICAgICAgICAgICAgICAgICAgIGRlbGV0ZSB0aGlzLm1lbWJlcnNbaV07XG4gICAgICAgICAgICAgICAgICAgIHRoaXMubGlzdF9tZW1iZXJzLnNwbGljZShpLCAxKTtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5vblBhcnRpY2lwYW50TGVmdChtZW1iZXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmICgkKHByZXMpLmZpbmQoJz54W3htbG5zPVwiaHR0cDovL2phYmJlci5vcmcvcHJvdG9jb2wvbXVjI3VzZXJcIl0+c3RhdHVzW2NvZGU9XCIzMDdcIl0nKS5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdraWNrZWQubXVjJywgW2Zyb21dKTtcbiAgICAgICAgICAgICAgICBpZiAodGhpcy5teXJvb21qaWQgPT09IGZyb20pIHtcbiAgICAgICAgICAgICAgICAgICAgWE1QUC5kaXNwb3NlQ29uZmVyZW5jZShmYWxzZSk7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50RW1pdHRlci5lbWl0KFhNUFBFdmVudHMuS0lDS0VEKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfSxcbiAgICAgICAgb25QcmVzZW5jZUVycm9yOiBmdW5jdGlvbiAocHJlcykge1xuICAgICAgICAgICAgdmFyIGZyb20gPSBwcmVzLmdldEF0dHJpYnV0ZSgnZnJvbScpO1xuICAgICAgICAgICAgaWYgKCQocHJlcykuZmluZCgnPmVycm9yW3R5cGU9XCJhdXRoXCJdPm5vdC1hdXRob3JpemVkW3htbG5zPVwidXJuOmlldGY6cGFyYW1zOnhtbDpuczp4bXBwLXN0YW56YXNcIl0nKS5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygnb24gcGFzc3dvcmQgcmVxdWlyZWQnLCBmcm9tKTtcbiAgICAgICAgICAgICAgICB2YXIgc2VsZiA9IHRoaXM7XG4gICAgICAgICAgICAgICAgVUkub25QYXNzd29yZFJlcWl1cmVkKGZ1bmN0aW9uICh2YWx1ZSkge1xuICAgICAgICAgICAgICAgICAgICBzZWxmLmRvSm9pbihmcm9tLCB2YWx1ZSk7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9IGVsc2UgaWYgKCQocHJlcykuZmluZChcbiAgICAgICAgICAgICAgICAnPmVycm9yW3R5cGU9XCJjYW5jZWxcIl0+bm90LWFsbG93ZWRbeG1sbnM9XCJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnhtcHAtc3Rhbnphc1wiXScpLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHZhciB0b0RvbWFpbiA9IFN0cm9waGUuZ2V0RG9tYWluRnJvbUppZChwcmVzLmdldEF0dHJpYnV0ZSgndG8nKSk7XG4gICAgICAgICAgICAgICAgaWYgKHRvRG9tYWluID09PSBjb25maWcuaG9zdHMuYW5vbnltb3VzZG9tYWluKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIHdlIGFyZSBjb25uZWN0ZWQgd2l0aCBhbm9ueW1vdXMgZG9tYWluIGFuZCBvbmx5IG5vbiBhbm9ueW1vdXMgdXNlcnMgY2FuIGNyZWF0ZSByb29tc1xuICAgICAgICAgICAgICAgICAgICAvLyB3ZSBtdXN0IGF1dGhvcml6ZSB0aGUgdXNlclxuICAgICAgICAgICAgICAgICAgICBYTVBQLnByb21wdExvZ2luKCk7XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS53YXJuKCdvblByZXNFcnJvciAnLCBwcmVzKTtcbiAgICAgICAgICAgICAgICAgICAgVUkubWVzc2FnZUhhbmRsZXIub3BlblJlcG9ydERpYWxvZyhudWxsLFxuICAgICAgICAgICAgICAgICAgICAgICAgJ09vcHMhIFNvbWV0aGluZyB3ZW50IHdyb25nIGFuZCB3ZSBjb3VsZG5gdCBjb25uZWN0IHRvIHRoZSBjb25mZXJlbmNlLicsXG4gICAgICAgICAgICAgICAgICAgICAgICBwcmVzKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUud2Fybignb25QcmVzRXJyb3IgJywgcHJlcyk7XG4gICAgICAgICAgICAgICAgVUkubWVzc2FnZUhhbmRsZXIub3BlblJlcG9ydERpYWxvZyhudWxsLFxuICAgICAgICAgICAgICAgICAgICAnT29wcyEgU29tZXRoaW5nIHdlbnQgd3JvbmcgYW5kIHdlIGNvdWxkbmB0IGNvbm5lY3QgdG8gdGhlIGNvbmZlcmVuY2UuJyxcbiAgICAgICAgICAgICAgICAgICAgcHJlcyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfSxcbiAgICAgICAgc2VuZE1lc3NhZ2U6IGZ1bmN0aW9uIChib2R5LCBuaWNrbmFtZSkge1xuICAgICAgICAgICAgdmFyIG1zZyA9ICRtc2coe3RvOiB0aGlzLnJvb21qaWQsIHR5cGU6ICdncm91cGNoYXQnfSk7XG4gICAgICAgICAgICBtc2cuYygnYm9keScsIGJvZHkpLnVwKCk7XG4gICAgICAgICAgICBpZiAobmlja25hbWUpIHtcbiAgICAgICAgICAgICAgICBtc2cuYygnbmljaycsIHt4bWxuczogJ2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL25pY2snfSkudChuaWNrbmFtZSkudXAoKS51cCgpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLnNlbmQobXNnKTtcbiAgICAgICAgICAgIEFQSS50cmlnZ2VyRXZlbnQoXCJvdXRnb2luZ01lc3NhZ2VcIiwge1wibWVzc2FnZVwiOiBib2R5fSk7XG4gICAgICAgIH0sXG4gICAgICAgIHNldFN1YmplY3Q6IGZ1bmN0aW9uIChzdWJqZWN0KSB7XG4gICAgICAgICAgICB2YXIgbXNnID0gJG1zZyh7dG86IHRoaXMucm9vbWppZCwgdHlwZTogJ2dyb3VwY2hhdCd9KTtcbiAgICAgICAgICAgIG1zZy5jKCdzdWJqZWN0Jywgc3ViamVjdCk7XG4gICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uc2VuZChtc2cpO1xuICAgICAgICAgICAgY29uc29sZS5sb2coXCJ0b3BpYyBjaGFuZ2VkIHRvIFwiICsgc3ViamVjdCk7XG4gICAgICAgIH0sXG4gICAgICAgIG9uTWVzc2FnZTogZnVuY3Rpb24gKG1zZykge1xuICAgICAgICAgICAgLy8gRklYTUU6IHRoaXMgaXMgYSBoYWNrLiBidXQgamluZ2xlIG9uIG11YyBtYWtlcyBuaWNrY2hhbmdlcyBoYXJkXG4gICAgICAgICAgICB2YXIgZnJvbSA9IG1zZy5nZXRBdHRyaWJ1dGUoJ2Zyb20nKTtcbiAgICAgICAgICAgIHZhciBuaWNrID0gJChtc2cpLmZpbmQoJz5uaWNrW3htbG5zPVwiaHR0cDovL2phYmJlci5vcmcvcHJvdG9jb2wvbmlja1wiXScpLnRleHQoKSB8fCBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChmcm9tKTtcblxuICAgICAgICAgICAgdmFyIHR4dCA9ICQobXNnKS5maW5kKCc+Ym9keScpLnRleHQoKTtcbiAgICAgICAgICAgIHZhciB0eXBlID0gbXNnLmdldEF0dHJpYnV0ZShcInR5cGVcIik7XG4gICAgICAgICAgICBpZiAodHlwZSA9PSBcImVycm9yXCIpIHtcbiAgICAgICAgICAgICAgICBVSS5jaGF0QWRkRXJyb3IoJChtc2cpLmZpbmQoJz50ZXh0JykudGV4dCgpLCB0eHQpO1xuICAgICAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgc3ViamVjdCA9ICQobXNnKS5maW5kKCc+c3ViamVjdCcpO1xuICAgICAgICAgICAgaWYgKHN1YmplY3QubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgdmFyIHN1YmplY3RUZXh0ID0gc3ViamVjdC50ZXh0KCk7XG4gICAgICAgICAgICAgICAgaWYgKHN1YmplY3RUZXh0IHx8IHN1YmplY3RUZXh0ID09IFwiXCIpIHtcbiAgICAgICAgICAgICAgICAgICAgVUkuY2hhdFNldFN1YmplY3Qoc3ViamVjdFRleHQpO1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZyhcIlN1YmplY3QgaXMgY2hhbmdlZCB0byBcIiArIHN1YmplY3RUZXh0KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cblxuICAgICAgICAgICAgaWYgKHR4dCkge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCdjaGF0JywgbmljaywgdHh0KTtcbiAgICAgICAgICAgICAgICBVSS51cGRhdGVDaGF0Q29udmVyc2F0aW9uKGZyb20sIG5pY2ssIHR4dCk7XG4gICAgICAgICAgICAgICAgaWYgKGZyb20gIT0gdGhpcy5teXJvb21qaWQpXG4gICAgICAgICAgICAgICAgICAgIEFQSS50cmlnZ2VyRXZlbnQoXCJpbmNvbWluZ01lc3NhZ2VcIixcbiAgICAgICAgICAgICAgICAgICAgICAgIHtcImZyb21cIjogZnJvbSwgXCJuaWNrXCI6IG5pY2ssIFwibWVzc2FnZVwiOiB0eHR9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9LFxuICAgICAgICBsb2NrUm9vbTogZnVuY3Rpb24gKGtleSwgb25TdWNjZXNzLCBvbkVycm9yLCBvbk5vdFN1cHBvcnRlZCkge1xuICAgICAgICAgICAgLy9odHRwOi8veG1wcC5vcmcvZXh0ZW5zaW9ucy94ZXAtMDA0NS5odG1sI3Jvb21jb25maWdcbiAgICAgICAgICAgIHZhciBvYiA9IHRoaXM7XG4gICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uc2VuZElRKCRpcSh7dG86IHRoaXMucm9vbWppZCwgdHlwZTogJ2dldCd9KS5jKCdxdWVyeScsIHt4bWxuczogJ2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL211YyNvd25lcid9KSxcbiAgICAgICAgICAgICAgICBmdW5jdGlvbiAocmVzKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmICgkKHJlcykuZmluZCgnPnF1ZXJ5PnhbeG1sbnM9XCJqYWJiZXI6eDpkYXRhXCJdPmZpZWxkW3Zhcj1cIm11YyNyb29tY29uZmlnX3Jvb21zZWNyZXRcIl0nKS5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBmb3Jtc3VibWl0ID0gJGlxKHt0bzogb2Iucm9vbWppZCwgdHlwZTogJ3NldCd9KS5jKCdxdWVyeScsIHt4bWxuczogJ2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL211YyNvd25lcid9KTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGZvcm1zdWJtaXQuYygneCcsIHt4bWxuczogJ2phYmJlcjp4OmRhdGEnLCB0eXBlOiAnc3VibWl0J30pO1xuICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXN1Ym1pdC5jKCdmaWVsZCcsIHsndmFyJzogJ0ZPUk1fVFlQRSd9KS5jKCd2YWx1ZScpLnQoJ2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL211YyNyb29tY29uZmlnJykudXAoKS51cCgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXN1Ym1pdC5jKCdmaWVsZCcsIHsndmFyJzogJ211YyNyb29tY29uZmlnX3Jvb21zZWNyZXQnfSkuYygndmFsdWUnKS50KGtleSkudXAoKS51cCgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gRml4ZXMgYSBidWcgaW4gcHJvc29keSAwLjkuKyBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL2x4bXBwZC9pc3N1ZXMvZGV0YWlsP2lkPTM3M1xuICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXN1Ym1pdC5jKCdmaWVsZCcsIHsndmFyJzogJ211YyNyb29tY29uZmlnX3dob2lzJ30pLmMoJ3ZhbHVlJykudCgnYW55b25lJykudXAoKS51cCgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gRklYTUU6IGlzIG11YyNyb29tY29uZmlnX3Bhc3N3b3JkcHJvdGVjdGVkcm9vbSByZXF1aXJlZD9cbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kSVEoZm9ybXN1Ym1pdCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBvblN1Y2Nlc3MsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgb25FcnJvcik7XG4gICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBvbk5vdFN1cHBvcnRlZCgpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSwgb25FcnJvcik7XG4gICAgICAgIH0sXG4gICAgICAgIGtpY2s6IGZ1bmN0aW9uIChqaWQpIHtcbiAgICAgICAgICAgIHZhciBraWNrSVEgPSAkaXEoe3RvOiB0aGlzLnJvb21qaWQsIHR5cGU6ICdzZXQnfSlcbiAgICAgICAgICAgICAgICAuYygncXVlcnknLCB7eG1sbnM6ICdodHRwOi8vamFiYmVyLm9yZy9wcm90b2NvbC9tdWMjYWRtaW4nfSlcbiAgICAgICAgICAgICAgICAuYygnaXRlbScsIHtuaWNrOiBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChqaWQpLCByb2xlOiAnbm9uZSd9KVxuICAgICAgICAgICAgICAgIC5jKCdyZWFzb24nKS50KCdZb3UgaGF2ZSBiZWVuIGtpY2tlZC4nKS51cCgpLnVwKCkudXAoKTtcblxuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLnNlbmRJUShcbiAgICAgICAgICAgICAgICBraWNrSVEsXG4gICAgICAgICAgICAgICAgZnVuY3Rpb24gKHJlc3VsdCkge1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygnS2ljayBwYXJ0aWNpcGFudCB3aXRoIGppZDogJywgamlkLCByZXN1bHQpO1xuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgZnVuY3Rpb24gKGVycm9yKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCdLaWNrIHBhcnRpY2lwYW50IGVycm9yOiAnLCBlcnJvcik7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgIH0sXG4gICAgICAgIHNlbmRQcmVzZW5jZTogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgdmFyIHByZXMgPSAkcHJlcyh7dG86IHRoaXMucHJlc01hcFsndG8nXSB9KTtcbiAgICAgICAgICAgIHByZXMuYygneCcsIHt4bWxuczogdGhpcy5wcmVzTWFwWyd4bnMnXX0pO1xuXG4gICAgICAgICAgICBpZiAodGhpcy5wcmVzTWFwWydwYXNzd29yZCddKSB7XG4gICAgICAgICAgICAgICAgcHJlcy5jKCdwYXNzd29yZCcpLnQodGhpcy5wcmVzTWFwWydwYXNzd29yZCddKS51cCgpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBwcmVzLnVwKCk7XG5cbiAgICAgICAgICAgIC8vIFNlbmQgWEVQLTAxMTUgJ2MnIHN0YW56YSB0aGF0IGNvbnRhaW5zIG91ciBjYXBhYmlsaXRpZXMgaW5mb1xuICAgICAgICAgICAgaWYgKHRoaXMuY29ubmVjdGlvbi5jYXBzKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLmNhcHMubm9kZSA9IGNvbmZpZy5jbGllbnROb2RlO1xuICAgICAgICAgICAgICAgIHByZXMuYygnYycsIHRoaXMuY29ubmVjdGlvbi5jYXBzLmdlbmVyYXRlQ2Fwc0F0dHJzKCkpLnVwKCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHByZXMuYygndXNlci1hZ2VudCcsIHt4bWxuczogJ2h0dHA6Ly9qaXRzaS5vcmcvaml0bWVldC91c2VyLWFnZW50J30pXG4gICAgICAgICAgICAgICAgLnQobmF2aWdhdG9yLnVzZXJBZ2VudCkudXAoKTtcblxuICAgICAgICAgICAgaWYgKHRoaXMucHJlc01hcFsnYnJpZGdlSXNEb3duJ10pIHtcbiAgICAgICAgICAgICAgICBwcmVzLmMoJ2JyaWRnZUlzRG93bicpLnVwKCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnByZXNNYXBbJ2VtYWlsJ10pIHtcbiAgICAgICAgICAgICAgICBwcmVzLmMoJ2VtYWlsJykudCh0aGlzLnByZXNNYXBbJ2VtYWlsJ10pLnVwKCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnByZXNNYXBbJ3VzZXJJZCddKSB7XG4gICAgICAgICAgICAgICAgcHJlcy5jKCd1c2VySWQnKS50KHRoaXMucHJlc01hcFsndXNlcklkJ10pLnVwKCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnByZXNNYXBbJ2Rpc3BsYXlOYW1lJ10pIHtcbiAgICAgICAgICAgICAgICAvLyBYRVAtMDE3MlxuICAgICAgICAgICAgICAgIHByZXMuYygnbmljaycsIHt4bWxuczogJ2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL25pY2snfSlcbiAgICAgICAgICAgICAgICAgICAgLnQodGhpcy5wcmVzTWFwWydkaXNwbGF5TmFtZSddKS51cCgpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGhpcy5wcmVzTWFwWydhdWRpb25zJ10pIHtcbiAgICAgICAgICAgICAgICBwcmVzLmMoJ2F1ZGlvbXV0ZWQnLCB7eG1sbnM6IHRoaXMucHJlc01hcFsnYXVkaW9ucyddfSlcbiAgICAgICAgICAgICAgICAgICAgLnQodGhpcy5wcmVzTWFwWydhdWRpb211dGVkJ10pLnVwKCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnByZXNNYXBbJ3ZpZGVvbnMnXSkge1xuICAgICAgICAgICAgICAgIHByZXMuYygndmlkZW9tdXRlZCcsIHt4bWxuczogdGhpcy5wcmVzTWFwWyd2aWRlb25zJ119KVxuICAgICAgICAgICAgICAgICAgICAudCh0aGlzLnByZXNNYXBbJ3ZpZGVvbXV0ZWQnXSkudXAoKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHRoaXMucHJlc01hcFsnc3RhdHNucyddKSB7XG4gICAgICAgICAgICAgICAgdmFyIHN0YXRzID0gcHJlcy5jKCdzdGF0cycsIHt4bWxuczogdGhpcy5wcmVzTWFwWydzdGF0c25zJ119KTtcbiAgICAgICAgICAgICAgICBmb3IgKHZhciBzdGF0IGluIHRoaXMucHJlc01hcFtcInN0YXRzXCJdKVxuICAgICAgICAgICAgICAgICAgICBpZiAodGhpcy5wcmVzTWFwW1wic3RhdHNcIl1bc3RhdF0gIT0gbnVsbClcbiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXRzLmMoXCJzdGF0XCIsIHtuYW1lOiBzdGF0LCB2YWx1ZTogdGhpcy5wcmVzTWFwW1wic3RhdHNcIl1bc3RhdF19KS51cCgpO1xuICAgICAgICAgICAgICAgIHByZXMudXAoKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHRoaXMucHJlc01hcFsncHJlemlucyddKSB7XG4gICAgICAgICAgICAgICAgcHJlcy5jKCdwcmV6aScsXG4gICAgICAgICAgICAgICAgICAgIHt4bWxuczogdGhpcy5wcmVzTWFwWydwcmV6aW5zJ10sXG4gICAgICAgICAgICAgICAgICAgICAgICAndXJsJzogdGhpcy5wcmVzTWFwWydwcmV6aXVybCddfSlcbiAgICAgICAgICAgICAgICAgICAgLmMoJ2N1cnJlbnQnKS50KHRoaXMucHJlc01hcFsncHJlemljdXJyZW50J10pLnVwKCkudXAoKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHRoaXMucHJlc01hcFsnZXRoZXJwYWRucyddKSB7XG4gICAgICAgICAgICAgICAgcHJlcy5jKCdldGhlcnBhZCcsIHt4bWxuczogdGhpcy5wcmVzTWFwWydldGhlcnBhZG5zJ119KVxuICAgICAgICAgICAgICAgICAgICAudCh0aGlzLnByZXNNYXBbJ2V0aGVycGFkbmFtZSddKS51cCgpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGhpcy5wcmVzTWFwWydtZWRpYW5zJ10pIHtcbiAgICAgICAgICAgICAgICBwcmVzLmMoJ21lZGlhJywge3htbG5zOiB0aGlzLnByZXNNYXBbJ21lZGlhbnMnXX0pO1xuICAgICAgICAgICAgICAgIHZhciBzb3VyY2VOdW1iZXIgPSAwO1xuICAgICAgICAgICAgICAgIE9iamVjdC5rZXlzKHRoaXMucHJlc01hcCkuZm9yRWFjaChmdW5jdGlvbiAoa2V5KSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChrZXkuaW5kZXhPZignc291cmNlJykgPj0gMCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgc291cmNlTnVtYmVyKys7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICBpZiAoc291cmNlTnVtYmVyID4gMClcbiAgICAgICAgICAgICAgICAgICAgZm9yICh2YXIgaSA9IDE7IGkgPD0gc291cmNlTnVtYmVyIC8gMzsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBwcmVzLmMoJ3NvdXJjZScsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAge3R5cGU6IHRoaXMucHJlc01hcFsnc291cmNlJyArIGkgKyAnX3R5cGUnXSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3NyYzogdGhpcy5wcmVzTWFwWydzb3VyY2UnICsgaSArICdfc3NyYyddLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb246IHRoaXMucHJlc01hcFsnc291cmNlJyArIGkgKyAnX2RpcmVjdGlvbiddXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8fCAnc2VuZHJlY3YnIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICkudXAoKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBwcmVzLnVwKCk7XG4vLyAgICAgICAgY29uc29sZS5kZWJ1ZyhwcmVzLnRvU3RyaW5nKCkpO1xuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLnNlbmQocHJlcyk7XG4gICAgICAgIH0sXG4gICAgICAgIGFkZERpc3BsYXlOYW1lVG9QcmVzZW5jZTogZnVuY3Rpb24gKGRpc3BsYXlOYW1lKSB7XG4gICAgICAgICAgICB0aGlzLnByZXNNYXBbJ2Rpc3BsYXlOYW1lJ10gPSBkaXNwbGF5TmFtZTtcbiAgICAgICAgfSxcbiAgICAgICAgYWRkTWVkaWFUb1ByZXNlbmNlOiBmdW5jdGlvbiAoc291cmNlTnVtYmVyLCBtdHlwZSwgc3NyY3MsIGRpcmVjdGlvbikge1xuICAgICAgICAgICAgaWYgKCF0aGlzLnByZXNNYXBbJ21lZGlhbnMnXSlcbiAgICAgICAgICAgICAgICB0aGlzLnByZXNNYXBbJ21lZGlhbnMnXSA9ICdodHRwOi8vZXN0b3MuZGUvbnMvbWpzJztcblxuICAgICAgICAgICAgdGhpcy5wcmVzTWFwWydzb3VyY2UnICsgc291cmNlTnVtYmVyICsgJ190eXBlJ10gPSBtdHlwZTtcbiAgICAgICAgICAgIHRoaXMucHJlc01hcFsnc291cmNlJyArIHNvdXJjZU51bWJlciArICdfc3NyYyddID0gc3NyY3M7XG4gICAgICAgICAgICB0aGlzLnByZXNNYXBbJ3NvdXJjZScgKyBzb3VyY2VOdW1iZXIgKyAnX2RpcmVjdGlvbiddID0gZGlyZWN0aW9uO1xuICAgICAgICB9LFxuICAgICAgICBjbGVhclByZXNlbmNlTWVkaWE6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgICAgIE9iamVjdC5rZXlzKHRoaXMucHJlc01hcCkuZm9yRWFjaChmdW5jdGlvbiAoa2V5KSB7XG4gICAgICAgICAgICAgICAgaWYgKGtleS5pbmRleE9mKCdzb3VyY2UnKSAhPSAtMSkge1xuICAgICAgICAgICAgICAgICAgICBkZWxldGUgc2VsZi5wcmVzTWFwW2tleV07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH0sXG4gICAgICAgIGFkZFByZXppVG9QcmVzZW5jZTogZnVuY3Rpb24gKHVybCwgY3VycmVudFNsaWRlKSB7XG4gICAgICAgICAgICB0aGlzLnByZXNNYXBbJ3ByZXppbnMnXSA9ICdodHRwOi8vaml0c2kub3JnL2ppdG1lZXQvcHJlemknO1xuICAgICAgICAgICAgdGhpcy5wcmVzTWFwWydwcmV6aXVybCddID0gdXJsO1xuICAgICAgICAgICAgdGhpcy5wcmVzTWFwWydwcmV6aWN1cnJlbnQnXSA9IGN1cnJlbnRTbGlkZTtcbiAgICAgICAgfSxcbiAgICAgICAgcmVtb3ZlUHJlemlGcm9tUHJlc2VuY2U6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGRlbGV0ZSB0aGlzLnByZXNNYXBbJ3ByZXppbnMnXTtcbiAgICAgICAgICAgIGRlbGV0ZSB0aGlzLnByZXNNYXBbJ3ByZXppdXJsJ107XG4gICAgICAgICAgICBkZWxldGUgdGhpcy5wcmVzTWFwWydwcmV6aWN1cnJlbnQnXTtcbiAgICAgICAgfSxcbiAgICAgICAgYWRkQ3VycmVudFNsaWRlVG9QcmVzZW5jZTogZnVuY3Rpb24gKGN1cnJlbnRTbGlkZSkge1xuICAgICAgICAgICAgdGhpcy5wcmVzTWFwWydwcmV6aWN1cnJlbnQnXSA9IGN1cnJlbnRTbGlkZTtcbiAgICAgICAgfSxcbiAgICAgICAgZ2V0UHJlemk6IGZ1bmN0aW9uIChyb29tamlkKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5wcmV6aU1hcFtyb29tamlkXTtcbiAgICAgICAgfSxcbiAgICAgICAgYWRkRXRoZXJwYWRUb1ByZXNlbmNlOiBmdW5jdGlvbiAoZXRoZXJwYWROYW1lKSB7XG4gICAgICAgICAgICB0aGlzLnByZXNNYXBbJ2V0aGVycGFkbnMnXSA9ICdodHRwOi8vaml0c2kub3JnL2ppdG1lZXQvZXRoZXJwYWQnO1xuICAgICAgICAgICAgdGhpcy5wcmVzTWFwWydldGhlcnBhZG5hbWUnXSA9IGV0aGVycGFkTmFtZTtcbiAgICAgICAgfSxcbiAgICAgICAgYWRkQXVkaW9JbmZvVG9QcmVzZW5jZTogZnVuY3Rpb24gKGlzTXV0ZWQpIHtcbiAgICAgICAgICAgIHRoaXMucHJlc01hcFsnYXVkaW9ucyddID0gJ2h0dHA6Ly9qaXRzaS5vcmcvaml0bWVldC9hdWRpbyc7XG4gICAgICAgICAgICB0aGlzLnByZXNNYXBbJ2F1ZGlvbXV0ZWQnXSA9IGlzTXV0ZWQudG9TdHJpbmcoKTtcbiAgICAgICAgfSxcbiAgICAgICAgYWRkVmlkZW9JbmZvVG9QcmVzZW5jZTogZnVuY3Rpb24gKGlzTXV0ZWQpIHtcbiAgICAgICAgICAgIHRoaXMucHJlc01hcFsndmlkZW9ucyddID0gJ2h0dHA6Ly9qaXRzaS5vcmcvaml0bWVldC92aWRlbyc7XG4gICAgICAgICAgICB0aGlzLnByZXNNYXBbJ3ZpZGVvbXV0ZWQnXSA9IGlzTXV0ZWQudG9TdHJpbmcoKTtcbiAgICAgICAgfSxcbiAgICAgICAgYWRkQ29ubmVjdGlvbkluZm9Ub1ByZXNlbmNlOiBmdW5jdGlvbiAoc3RhdHMpIHtcbiAgICAgICAgICAgIHRoaXMucHJlc01hcFsnc3RhdHNucyddID0gJ2h0dHA6Ly9qaXRzaS5vcmcvaml0bWVldC9zdGF0cyc7XG4gICAgICAgICAgICB0aGlzLnByZXNNYXBbJ3N0YXRzJ10gPSBzdGF0cztcbiAgICAgICAgfSxcbiAgICAgICAgZmluZEppZEZyb21SZXNvdXJjZTogZnVuY3Rpb24gKHJlc291cmNlSmlkKSB7XG4gICAgICAgICAgICBpZiAocmVzb3VyY2VKaWQgJiZcbiAgICAgICAgICAgICAgICByZXNvdXJjZUppZCA9PT0gU3Ryb3BoZS5nZXRSZXNvdXJjZUZyb21KaWQodGhpcy5teXJvb21qaWQpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXMubXlyb29tamlkO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdmFyIHBlZXJKaWQgPSBudWxsO1xuICAgICAgICAgICAgT2JqZWN0LmtleXModGhpcy5tZW1iZXJzKS5zb21lKGZ1bmN0aW9uIChqaWQpIHtcbiAgICAgICAgICAgICAgICBwZWVySmlkID0gamlkO1xuICAgICAgICAgICAgICAgIHJldHVybiBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChqaWQpID09PSByZXNvdXJjZUppZDtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgcmV0dXJuIHBlZXJKaWQ7XG4gICAgICAgIH0sXG4gICAgICAgIGFkZEJyaWRnZUlzRG93blRvUHJlc2VuY2U6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHRoaXMucHJlc01hcFsnYnJpZGdlSXNEb3duJ10gPSB0cnVlO1xuICAgICAgICB9LFxuICAgICAgICBhZGRFbWFpbFRvUHJlc2VuY2U6IGZ1bmN0aW9uIChlbWFpbCkge1xuICAgICAgICAgICAgdGhpcy5wcmVzTWFwWydlbWFpbCddID0gZW1haWw7XG4gICAgICAgIH0sXG4gICAgICAgIGFkZFVzZXJJZFRvUHJlc2VuY2U6IGZ1bmN0aW9uICh1c2VySWQpIHtcbiAgICAgICAgICAgIHRoaXMucHJlc01hcFsndXNlcklkJ10gPSB1c2VySWQ7XG4gICAgICAgIH0sXG4gICAgICAgIGlzTW9kZXJhdG9yOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5yb2xlID09PSAnbW9kZXJhdG9yJztcbiAgICAgICAgfSxcbiAgICAgICAgZ2V0TWVtYmVyUm9sZTogZnVuY3Rpb24gKHBlZXJKaWQpIHtcbiAgICAgICAgICAgIGlmICh0aGlzLm1lbWJlcnNbcGVlckppZF0pIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5tZW1iZXJzW3BlZXJKaWRdLnJvbGU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfSxcbiAgICAgICAgb25QYXJ0aWNpcGFudExlZnQ6IGZ1bmN0aW9uIChqaWQpIHtcbiAgICAgICAgICAgIFVJLm9uTXVjTGVmdChqaWQpO1xuXG4gICAgICAgICAgICBBUEkudHJpZ2dlckV2ZW50KFwicGFydGljaXBhbnRMZWZ0XCIsIHtqaWQ6IGppZH0pO1xuXG4gICAgICAgICAgICBkZWxldGUgamlkMlNzcmNbamlkXTtcblxuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLmppbmdsZS50ZXJtaW5hdGVCeUppZChqaWQpO1xuXG4gICAgICAgICAgICBpZiAodGhpcy5nZXRQcmV6aShqaWQpKSB7XG4gICAgICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcigncHJlc2VudGF0aW9ucmVtb3ZlZC5tdWMnLFxuICAgICAgICAgICAgICAgICAgICBbamlkLCB0aGlzLmdldFByZXppKGppZCldKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgTW9kZXJhdG9yLm9uTXVjTGVmdChqaWQpO1xuICAgICAgICB9LFxuICAgICAgICBwYXJzZVByZXNlbmNlOiBmdW5jdGlvbiAoZnJvbSwgbWVtZWJlciwgcHJlcykge1xuICAgICAgICAgICAgaWYoJChwcmVzKS5maW5kKFwiPmJyaWRnZUlzRG93blwiKS5sZW5ndGggPiAwICYmICFicmlkZ2VJc0Rvd24pIHtcbiAgICAgICAgICAgICAgICBicmlkZ2VJc0Rvd24gPSB0cnVlO1xuICAgICAgICAgICAgICAgIGV2ZW50RW1pdHRlci5lbWl0KFhNUFBFdmVudHMuQlJJREdFX0RPV04pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZihtZW1lYmVyLmlzRm9jdXMpXG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuXG4gICAgICAgICAgICAvLyBSZW1vdmUgb2xkIHNzcmNzIGNvbWluZyBmcm9tIHRoZSBqaWRcbiAgICAgICAgICAgIE9iamVjdC5rZXlzKHNzcmMyamlkKS5mb3JFYWNoKGZ1bmN0aW9uIChzc3JjKSB7XG4gICAgICAgICAgICAgICAgaWYgKHNzcmMyamlkW3NzcmNdID09IGppZCkge1xuICAgICAgICAgICAgICAgICAgICBkZWxldGUgc3NyYzJqaWRbc3NyY107XG4gICAgICAgICAgICAgICAgICAgIGRlbGV0ZSBzc3JjMnZpZGVvVHlwZVtzc3JjXTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgdmFyIGNoYW5nZWRTdHJlYW1zID0gW107XG4gICAgICAgICAgICAkKHByZXMpLmZpbmQoJz5tZWRpYVt4bWxucz1cImh0dHA6Ly9lc3Rvcy5kZS9ucy9tanNcIl0+c291cmNlJykuZWFjaChmdW5jdGlvbiAoaWR4LCBzc3JjKSB7XG4gICAgICAgICAgICAgICAgLy9jb25zb2xlLmxvZyhqaWQsICdhc3NvYyBzc3JjJywgc3NyYy5nZXRBdHRyaWJ1dGUoJ3R5cGUnKSwgc3NyYy5nZXRBdHRyaWJ1dGUoJ3NzcmMnKSk7XG4gICAgICAgICAgICAgICAgdmFyIHNzcmNWID0gc3NyYy5nZXRBdHRyaWJ1dGUoJ3NzcmMnKTtcbiAgICAgICAgICAgICAgICBzc3JjMmppZFtzc3JjVl0gPSBmcm9tO1xuICAgICAgICAgICAgICAgIG5vdFJlY2VpdmVkU1NSQ3MucHVzaChzc3JjVik7XG5cbiAgICAgICAgICAgICAgICB2YXIgdHlwZSA9IHNzcmMuZ2V0QXR0cmlidXRlKCd0eXBlJyk7XG4gICAgICAgICAgICAgICAgc3NyYzJ2aWRlb1R5cGVbc3NyY1ZdID0gdHlwZTtcblxuICAgICAgICAgICAgICAgIHZhciBkaXJlY3Rpb24gPSBzc3JjLmdldEF0dHJpYnV0ZSgnZGlyZWN0aW9uJyk7XG5cbiAgICAgICAgICAgICAgICBjaGFuZ2VkU3RyZWFtcy5wdXNoKHt0eXBlOiB0eXBlLCBkaXJlY3Rpb246IGRpcmVjdGlvbn0pO1xuXG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgZXZlbnRFbWl0dGVyLmVtaXQoWE1QUEV2ZW50cy5DSEFOR0VEX1NUUkVBTVMsIGZyb20sIGNoYW5nZWRTdHJlYW1zKTtcblxuICAgICAgICAgICAgdmFyIGRpc3BsYXlOYW1lID0gIWNvbmZpZy5kaXNwbGF5Smlkc1xuICAgICAgICAgICAgICAgID8gbWVtZWJlci5kaXNwbGF5TmFtZSA6IFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGZyb20pO1xuXG4gICAgICAgICAgICBpZiAoZGlzcGxheU5hbWUgJiYgZGlzcGxheU5hbWUubGVuZ3RoID4gMClcbiAgICAgICAgICAgIHtcbi8vICAgICAgICAgICAgICAgICQoZG9jdW1lbnQpLnRyaWdnZXIoJ2Rpc3BsYXluYW1lY2hhbmdlZCcsXG4vLyAgICAgICAgICAgICAgICAgICAgW2ppZCwgZGlzcGxheU5hbWVdKTtcbiAgICAgICAgICAgICAgICBldmVudEVtaXR0ZXIuZW1pdChYTVBQRXZlbnRzLkRJU1BMQVlfTkFNRV9DSEFOR0VELCBmcm9tLCBkaXNwbGF5TmFtZSk7XG4gICAgICAgICAgICB9XG5cblxuICAgICAgICAgICAgdmFyIGlkID0gJChwcmVzKS5maW5kKCc+dXNlcklEJykudGV4dCgpO1xuICAgICAgICAgICAgdmFyIGVtYWlsID0gJChwcmVzKS5maW5kKCc+ZW1haWwnKTtcbiAgICAgICAgICAgIGlmKGVtYWlsLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgICAgICBpZCA9IGVtYWlsLnRleHQoKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZXZlbnRFbWl0dGVyLmVtaXQoWE1QUEV2ZW50cy5VU0VSX0lEX0NIQU5HRUQsIGZyb20sIGlkKTtcbiAgICAgICAgfVxuICAgIH0pO1xufTtcblxuIiwiLyoganNoaW50IC1XMTE3ICovXG5cbnZhciBKaW5nbGVTZXNzaW9uID0gcmVxdWlyZShcIi4vSmluZ2xlU2Vzc2lvblwiKTtcblxuZnVuY3Rpb24gQ2FsbEluY29taW5nSmluZ2xlKHNpZCwgY29ubmVjdGlvbikge1xuICAgIHZhciBzZXNzID0gY29ubmVjdGlvbi5qaW5nbGUuc2Vzc2lvbnNbc2lkXTtcblxuICAgIC8vIFRPRE86IGRvIHdlIGNoZWNrIGFjdGl2ZWNhbGwgPT0gbnVsbD9cbiAgICBhY3RpdmVjYWxsID0gc2VzcztcblxuICAgIHN0YXRpc3RpY3Mub25Db25mZXJlbmNlQ3JlYXRlZChzZXNzKTtcbiAgICBSVEMub25Db25mZXJlbmNlQ3JlYXRlZChzZXNzKTtcblxuICAgIC8vIFRPRE86IGNoZWNrIGFmZmlsaWF0aW9uIGFuZC9vciByb2xlXG4gICAgY29uc29sZS5sb2coJ2VtdWMgZGF0YSBmb3InLCBzZXNzLnBlZXJqaWQsIGNvbm5lY3Rpb24uZW11Yy5tZW1iZXJzW3Nlc3MucGVlcmppZF0pO1xuICAgIHNlc3MudXNlZHJpcCA9IHRydWU7IC8vIG5vdC1zby1uYWl2ZSB0cmlja2xlIGljZVxuICAgIHNlc3Muc2VuZEFuc3dlcigpO1xuICAgIHNlc3MuYWNjZXB0KCk7XG5cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24oWE1QUClcbntcbiAgICBTdHJvcGhlLmFkZENvbm5lY3Rpb25QbHVnaW4oJ2ppbmdsZScsIHtcbiAgICAgICAgY29ubmVjdGlvbjogbnVsbCxcbiAgICAgICAgc2Vzc2lvbnM6IHt9LFxuICAgICAgICBqaWQyc2Vzc2lvbjoge30sXG4gICAgICAgIGljZV9jb25maWc6IHtpY2VTZXJ2ZXJzOiBbXX0sXG4gICAgICAgIHBjX2NvbnN0cmFpbnRzOiB7fSxcbiAgICAgICAgbWVkaWFfY29uc3RyYWludHM6IHtcbiAgICAgICAgICAgIG1hbmRhdG9yeToge1xuICAgICAgICAgICAgICAgICdPZmZlclRvUmVjZWl2ZUF1ZGlvJzogdHJ1ZSxcbiAgICAgICAgICAgICAgICAnT2ZmZXJUb1JlY2VpdmVWaWRlbyc6IHRydWVcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIE1vekRvbnRPZmZlckRhdGFDaGFubmVsOiB0cnVlIHdoZW4gdGhpcyBpcyBmaXJlZm94XG4gICAgICAgIH0sXG4gICAgICAgIGluaXQ6IGZ1bmN0aW9uIChjb25uKSB7XG4gICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24gPSBjb25uO1xuICAgICAgICAgICAgaWYgKHRoaXMuY29ubmVjdGlvbi5kaXNjbykge1xuICAgICAgICAgICAgICAgIC8vIGh0dHA6Ly94bXBwLm9yZy9leHRlbnNpb25zL3hlcC0wMTY3Lmh0bWwjc3VwcG9ydFxuICAgICAgICAgICAgICAgIC8vIGh0dHA6Ly94bXBwLm9yZy9leHRlbnNpb25zL3hlcC0wMTc2Lmh0bWwjc3VwcG9ydFxuICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5kaXNjby5hZGRGZWF0dXJlKCd1cm46eG1wcDpqaW5nbGU6MScpO1xuICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5kaXNjby5hZGRGZWF0dXJlKCd1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6MScpO1xuICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5kaXNjby5hZGRGZWF0dXJlKCd1cm46eG1wcDpqaW5nbGU6dHJhbnNwb3J0czppY2UtdWRwOjEnKTtcbiAgICAgICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uZGlzY28uYWRkRmVhdHVyZSgndXJuOnhtcHA6amluZ2xlOmFwcHM6cnRwOmF1ZGlvJyk7XG4gICAgICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLmRpc2NvLmFkZEZlYXR1cmUoJ3Vybjp4bXBwOmppbmdsZTphcHBzOnJ0cDp2aWRlbycpO1xuXG5cbiAgICAgICAgICAgICAgICAvLyB0aGlzIGlzIGRlYWx0IHdpdGggYnkgU0RQIE8vQSBzbyB3ZSBkb24ndCBuZWVkIHRvIGFubm91Y2UgdGhpc1xuICAgICAgICAgICAgICAgIC8vdGhpcy5jb25uZWN0aW9uLmRpc2NvLmFkZEZlYXR1cmUoJ3Vybjp4bXBwOmppbmdsZTphcHBzOnJ0cDpydGNwLWZiOjAnKTsgLy8gWEVQLTAyOTNcbiAgICAgICAgICAgICAgICAvL3RoaXMuY29ubmVjdGlvbi5kaXNjby5hZGRGZWF0dXJlKCd1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6cnRwLWhkcmV4dDowJyk7IC8vIFhFUC0wMjk0XG4gICAgICAgICAgICAgICAgaWYgKGNvbmZpZy51c2VSdGNwTXV4KSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5kaXNjby5hZGRGZWF0dXJlKCd1cm46aWV0ZjpyZmM6NTc2MScpOyAvLyBydGNwLW11eFxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAoY29uZmlnLnVzZUJ1bmRsZSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uZGlzY28uYWRkRmVhdHVyZSgndXJuOmlldGY6cmZjOjU4ODgnKTsgLy8gYT1ncm91cCwgZS5nLiBidW5kbGVcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgLy90aGlzLmNvbm5lY3Rpb24uZGlzY28uYWRkRmVhdHVyZSgndXJuOmlldGY6cmZjOjU1NzYnKTsgLy8gYT1zc3JjXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uYWRkSGFuZGxlcih0aGlzLm9uSmluZ2xlLmJpbmQodGhpcyksICd1cm46eG1wcDpqaW5nbGU6MScsICdpcScsICdzZXQnLCBudWxsLCBudWxsKTtcbiAgICAgICAgfSxcbiAgICAgICAgb25KaW5nbGU6IGZ1bmN0aW9uIChpcSkge1xuICAgICAgICAgICAgdmFyIHNpZCA9ICQoaXEpLmZpbmQoJ2ppbmdsZScpLmF0dHIoJ3NpZCcpO1xuICAgICAgICAgICAgdmFyIGFjdGlvbiA9ICQoaXEpLmZpbmQoJ2ppbmdsZScpLmF0dHIoJ2FjdGlvbicpO1xuICAgICAgICAgICAgdmFyIGZyb21KaWQgPSBpcS5nZXRBdHRyaWJ1dGUoJ2Zyb20nKTtcbiAgICAgICAgICAgIC8vIHNlbmQgYWNrIGZpcnN0XG4gICAgICAgICAgICB2YXIgYWNrID0gJGlxKHt0eXBlOiAncmVzdWx0JyxcbiAgICAgICAgICAgICAgICB0bzogZnJvbUppZCxcbiAgICAgICAgICAgICAgICBpZDogaXEuZ2V0QXR0cmlidXRlKCdpZCcpXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdvbiBqaW5nbGUgJyArIGFjdGlvbiArICcgZnJvbSAnICsgZnJvbUppZCwgaXEpO1xuICAgICAgICAgICAgdmFyIHNlc3MgPSB0aGlzLnNlc3Npb25zW3NpZF07XG4gICAgICAgICAgICBpZiAoJ3Nlc3Npb24taW5pdGlhdGUnICE9IGFjdGlvbikge1xuICAgICAgICAgICAgICAgIGlmIChzZXNzID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIGFjay50eXBlID0gJ2Vycm9yJztcbiAgICAgICAgICAgICAgICAgICAgYWNrLmMoJ2Vycm9yJywge3R5cGU6ICdjYW5jZWwnfSlcbiAgICAgICAgICAgICAgICAgICAgICAgIC5jKCdpdGVtLW5vdC1mb3VuZCcsIHt4bWxuczogJ3VybjppZXRmOnBhcmFtczp4bWw6bnM6eG1wcC1zdGFuemFzJ30pLnVwKClcbiAgICAgICAgICAgICAgICAgICAgICAgIC5jKCd1bmtub3duLXNlc3Npb24nLCB7eG1sbnM6ICd1cm46eG1wcDpqaW5nbGU6ZXJyb3JzOjEnfSk7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kKGFjayk7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAvLyBjb21wYXJlIGZyb20gdG8gc2Vzcy5wZWVyamlkIChiYXJlIGppZCBjb21wYXJpc29uIGZvciBsYXRlciBjb21wYXQgd2l0aCBtZXNzYWdlLW1vZGUpXG4gICAgICAgICAgICAgICAgLy8gbG9jYWwgamlkIGlzIG5vdCBjaGVja2VkXG4gICAgICAgICAgICAgICAgaWYgKFN0cm9waGUuZ2V0QmFyZUppZEZyb21KaWQoZnJvbUppZCkgIT0gU3Ryb3BoZS5nZXRCYXJlSmlkRnJvbUppZChzZXNzLnBlZXJqaWQpKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybignamlkIG1pc21hdGNoIGZvciBzZXNzaW9uIGlkJywgc2lkLCBmcm9tSmlkLCBzZXNzLnBlZXJqaWQpO1xuICAgICAgICAgICAgICAgICAgICBhY2sudHlwZSA9ICdlcnJvcic7XG4gICAgICAgICAgICAgICAgICAgIGFjay5jKCdlcnJvcicsIHt0eXBlOiAnY2FuY2VsJ30pXG4gICAgICAgICAgICAgICAgICAgICAgICAuYygnaXRlbS1ub3QtZm91bmQnLCB7eG1sbnM6ICd1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnhtcHAtc3Rhbnphcyd9KS51cCgpXG4gICAgICAgICAgICAgICAgICAgICAgICAuYygndW5rbm93bi1zZXNzaW9uJywge3htbG5zOiAndXJuOnhtcHA6amluZ2xlOmVycm9yczoxJ30pO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uc2VuZChhY2spO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9IGVsc2UgaWYgKHNlc3MgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgIC8vIGV4aXN0aW5nIHNlc3Npb24gd2l0aCBzYW1lIHNlc3Npb24gaWRcbiAgICAgICAgICAgICAgICAvLyB0aGlzIG1pZ2h0IGJlIG91dC1vZi1vcmRlciBpZiB0aGUgc2Vzcy5wZWVyamlkIGlzIHRoZSBzYW1lIGFzIGZyb21cbiAgICAgICAgICAgICAgICBhY2sudHlwZSA9ICdlcnJvcic7XG4gICAgICAgICAgICAgICAgYWNrLmMoJ2Vycm9yJywge3R5cGU6ICdjYW5jZWwnfSlcbiAgICAgICAgICAgICAgICAgICAgLmMoJ3NlcnZpY2UtdW5hdmFpbGFibGUnLCB7eG1sbnM6ICd1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnhtcHAtc3Rhbnphcyd9KS51cCgpO1xuICAgICAgICAgICAgICAgIGNvbnNvbGUud2FybignZHVwbGljYXRlIHNlc3Npb24gaWQnLCBzaWQpO1xuICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kKGFjayk7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBGSVhNRTogY2hlY2sgZm9yIGEgZGVmaW5lZCBhY3Rpb25cbiAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kKGFjayk7XG4gICAgICAgICAgICAvLyBzZWUgaHR0cDovL3htcHAub3JnL2V4dGVuc2lvbnMveGVwLTAxNjYuaHRtbCNjb25jZXB0cy1zZXNzaW9uXG4gICAgICAgICAgICBzd2l0Y2ggKGFjdGlvbikge1xuICAgICAgICAgICAgICAgIGNhc2UgJ3Nlc3Npb24taW5pdGlhdGUnOlxuICAgICAgICAgICAgICAgICAgICBzZXNzID0gbmV3IEppbmdsZVNlc3Npb24oXG4gICAgICAgICAgICAgICAgICAgICAgICAkKGlxKS5hdHRyKCd0bycpLCAkKGlxKS5maW5kKCdqaW5nbGUnKS5hdHRyKCdzaWQnKSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbiwgWE1QUCk7XG4gICAgICAgICAgICAgICAgICAgIC8vIGNvbmZpZ3VyZSBzZXNzaW9uXG5cbiAgICAgICAgICAgICAgICAgICAgc2Vzcy5tZWRpYV9jb25zdHJhaW50cyA9IHRoaXMubWVkaWFfY29uc3RyYWludHM7XG4gICAgICAgICAgICAgICAgICAgIHNlc3MucGNfY29uc3RyYWludHMgPSB0aGlzLnBjX2NvbnN0cmFpbnRzO1xuICAgICAgICAgICAgICAgICAgICBzZXNzLmljZV9jb25maWcgPSB0aGlzLmljZV9jb25maWc7XG5cbiAgICAgICAgICAgICAgICAgICAgc2Vzcy5pbml0aWF0ZShmcm9tSmlkLCBmYWxzZSk7XG4gICAgICAgICAgICAgICAgICAgIC8vIEZJWE1FOiBzZXRSZW1vdGVEZXNjcmlwdGlvbiBzaG91bGQgb25seSBiZSBkb25lIHdoZW4gdGhpcyBjYWxsIGlzIHRvIGJlIGFjY2VwdGVkXG4gICAgICAgICAgICAgICAgICAgIHNlc3Muc2V0UmVtb3RlRGVzY3JpcHRpb24oJChpcSkuZmluZCgnPmppbmdsZScpLCAnb2ZmZXInKTtcblxuICAgICAgICAgICAgICAgICAgICB0aGlzLnNlc3Npb25zW3Nlc3Muc2lkXSA9IHNlc3M7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuamlkMnNlc3Npb25bc2Vzcy5wZWVyamlkXSA9IHNlc3M7XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gdGhlIGNhbGxiYWNrIHNob3VsZCBlaXRoZXJcbiAgICAgICAgICAgICAgICAgICAgLy8gLnNlbmRBbnN3ZXIgYW5kIC5hY2NlcHRcbiAgICAgICAgICAgICAgICAgICAgLy8gb3IgLnNlbmRUZXJtaW5hdGUgLS0gbm90IG5lY2Vzc2FyaWx5IHN5bmNocm9udXNcbiAgICAgICAgICAgICAgICAgICAgQ2FsbEluY29taW5nSmluZ2xlKHNlc3Muc2lkLCB0aGlzLmNvbm5lY3Rpb24pO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlICdzZXNzaW9uLWFjY2VwdCc6XG4gICAgICAgICAgICAgICAgICAgIHNlc3Muc2V0UmVtb3RlRGVzY3JpcHRpb24oJChpcSkuZmluZCgnPmppbmdsZScpLCAnYW5zd2VyJyk7XG4gICAgICAgICAgICAgICAgICAgIHNlc3MuYWNjZXB0KCk7XG4gICAgICAgICAgICAgICAgICAgICQoZG9jdW1lbnQpLnRyaWdnZXIoJ2NhbGxhY2NlcHRlZC5qaW5nbGUnLCBbc2Vzcy5zaWRdKTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgY2FzZSAnc2Vzc2lvbi10ZXJtaW5hdGUnOlxuICAgICAgICAgICAgICAgICAgICAvLyBJZiB0aGlzIGlzIG5vdCB0aGUgZm9jdXMgc2VuZGluZyB0aGUgdGVybWluYXRlLCB3ZSBoYXZlXG4gICAgICAgICAgICAgICAgICAgIC8vIG5vdGhpbmcgbW9yZSB0byBkbyBoZXJlLlxuICAgICAgICAgICAgICAgICAgICBpZiAoT2JqZWN0LmtleXModGhpcy5zZXNzaW9ucykubGVuZ3RoIDwgMVxuICAgICAgICAgICAgICAgICAgICAgICAgfHwgISh0aGlzLnNlc3Npb25zW09iamVjdC5rZXlzKHRoaXMuc2Vzc2lvbnMpWzBdXVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluc3RhbmNlb2YgSmluZ2xlU2Vzc2lvbikpXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCd0ZXJtaW5hdGluZy4uLicsIHNlc3Muc2lkKTtcbiAgICAgICAgICAgICAgICAgICAgc2Vzcy50ZXJtaW5hdGUoKTtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy50ZXJtaW5hdGUoc2Vzcy5zaWQpO1xuICAgICAgICAgICAgICAgICAgICBpZiAoJChpcSkuZmluZCgnPmppbmdsZT5yZWFzb24nKS5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICQoZG9jdW1lbnQpLnRyaWdnZXIoJ2NhbGx0ZXJtaW5hdGVkLmppbmdsZScsIFtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXNzLnNpZCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXNzLnBlZXJqaWQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgJChpcSkuZmluZCgnPmppbmdsZT5yZWFzb24+OmZpcnN0JylbMF0udGFnTmFtZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAkKGlxKS5maW5kKCc+amluZ2xlPnJlYXNvbj50ZXh0JykudGV4dCgpXG4gICAgICAgICAgICAgICAgICAgICAgICBdKTtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICQoZG9jdW1lbnQpLnRyaWdnZXIoJ2NhbGx0ZXJtaW5hdGVkLmppbmdsZScsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgW3Nlc3Muc2lkLCBzZXNzLnBlZXJqaWRdKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlICd0cmFuc3BvcnQtaW5mbyc6XG4gICAgICAgICAgICAgICAgICAgIHNlc3MuYWRkSWNlQ2FuZGlkYXRlKCQoaXEpLmZpbmQoJz5qaW5nbGU+Y29udGVudCcpKTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgY2FzZSAnc2Vzc2lvbi1pbmZvJzpcbiAgICAgICAgICAgICAgICAgICAgdmFyIGFmZmVjdGVkO1xuICAgICAgICAgICAgICAgICAgICBpZiAoJChpcSkuZmluZCgnPmppbmdsZT5yaW5naW5nW3htbG5zPVwidXJuOnhtcHA6amluZ2xlOmFwcHM6cnRwOmluZm86MVwiXScpLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcigncmluZ2luZy5qaW5nbGUnLCBbc2Vzcy5zaWRdKTtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIGlmICgkKGlxKS5maW5kKCc+amluZ2xlPm11dGVbeG1sbnM9XCJ1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6aW5mbzoxXCJdJykubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBhZmZlY3RlZCA9ICQoaXEpLmZpbmQoJz5qaW5nbGU+bXV0ZVt4bWxucz1cInVybjp4bXBwOmppbmdsZTphcHBzOnJ0cDppbmZvOjFcIl0nKS5hdHRyKCduYW1lJyk7XG4gICAgICAgICAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdtdXRlLmppbmdsZScsIFtzZXNzLnNpZCwgYWZmZWN0ZWRdKTtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIGlmICgkKGlxKS5maW5kKCc+amluZ2xlPnVubXV0ZVt4bWxucz1cInVybjp4bXBwOmppbmdsZTphcHBzOnJ0cDppbmZvOjFcIl0nKS5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGFmZmVjdGVkID0gJChpcSkuZmluZCgnPmppbmdsZT51bm11dGVbeG1sbnM9XCJ1cm46eG1wcDpqaW5nbGU6YXBwczpydHA6aW5mbzoxXCJdJykuYXR0cignbmFtZScpO1xuICAgICAgICAgICAgICAgICAgICAgICAgJChkb2N1bWVudCkudHJpZ2dlcigndW5tdXRlLmppbmdsZScsIFtzZXNzLnNpZCwgYWZmZWN0ZWRdKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlICdhZGRzb3VyY2UnOiAvLyBGSVhNRTogcHJvcHJpZXRhcnksIHVuLWppbmdsZWlzaFxuICAgICAgICAgICAgICAgIGNhc2UgJ3NvdXJjZS1hZGQnOiAvLyBGSVhNRTogcHJvcHJpZXRhcnlcbiAgICAgICAgICAgICAgICAgICAgc2Vzcy5hZGRTb3VyY2UoJChpcSkuZmluZCgnPmppbmdsZT5jb250ZW50JyksIGZyb21KaWQpO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlICdyZW1vdmVzb3VyY2UnOiAvLyBGSVhNRTogcHJvcHJpZXRhcnksIHVuLWppbmdsZWlzaFxuICAgICAgICAgICAgICAgIGNhc2UgJ3NvdXJjZS1yZW1vdmUnOiAvLyBGSVhNRTogcHJvcHJpZXRhcnlcbiAgICAgICAgICAgICAgICAgICAgc2Vzcy5yZW1vdmVTb3VyY2UoJChpcSkuZmluZCgnPmppbmdsZT5jb250ZW50JyksIGZyb21KaWQpO1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oJ2ppbmdsZSBhY3Rpb24gbm90IGltcGxlbWVudGVkJywgYWN0aW9uKTtcbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfSxcbiAgICAgICAgaW5pdGlhdGU6IGZ1bmN0aW9uIChwZWVyamlkLCBteWppZCkgeyAvLyBpbml0aWF0ZSBhIG5ldyBqaW5nbGVzZXNzaW9uIHRvIHBlZXJqaWRcbiAgICAgICAgICAgIHZhciBzZXNzID0gbmV3IEppbmdsZVNlc3Npb24obXlqaWQgfHwgdGhpcy5jb25uZWN0aW9uLmppZCxcbiAgICAgICAgICAgICAgICBNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zdWJzdHIoMiwgMTIpLCAvLyByYW5kb20gc3RyaW5nXG4gICAgICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLCBYTVBQKTtcbiAgICAgICAgICAgIC8vIGNvbmZpZ3VyZSBzZXNzaW9uXG5cbiAgICAgICAgICAgIHNlc3MubWVkaWFfY29uc3RyYWludHMgPSB0aGlzLm1lZGlhX2NvbnN0cmFpbnRzO1xuICAgICAgICAgICAgc2Vzcy5wY19jb25zdHJhaW50cyA9IHRoaXMucGNfY29uc3RyYWludHM7XG4gICAgICAgICAgICBzZXNzLmljZV9jb25maWcgPSB0aGlzLmljZV9jb25maWc7XG5cbiAgICAgICAgICAgIHNlc3MuaW5pdGlhdGUocGVlcmppZCwgdHJ1ZSk7XG4gICAgICAgICAgICB0aGlzLnNlc3Npb25zW3Nlc3Muc2lkXSA9IHNlc3M7XG4gICAgICAgICAgICB0aGlzLmppZDJzZXNzaW9uW3Nlc3MucGVlcmppZF0gPSBzZXNzO1xuICAgICAgICAgICAgc2Vzcy5zZW5kT2ZmZXIoKTtcbiAgICAgICAgICAgIHJldHVybiBzZXNzO1xuICAgICAgICB9LFxuICAgICAgICB0ZXJtaW5hdGU6IGZ1bmN0aW9uIChzaWQsIHJlYXNvbiwgdGV4dCkgeyAvLyB0ZXJtaW5hdGUgYnkgc2Vzc2lvbmlkIChvciBhbGwgc2Vzc2lvbnMpXG4gICAgICAgICAgICBpZiAoc2lkID09PSBudWxsIHx8IHNpZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgZm9yIChzaWQgaW4gdGhpcy5zZXNzaW9ucykge1xuICAgICAgICAgICAgICAgICAgICBpZiAodGhpcy5zZXNzaW9uc1tzaWRdLnN0YXRlICE9ICdlbmRlZCcpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuc2Vzc2lvbnNbc2lkXS5zZW5kVGVybWluYXRlKHJlYXNvbiB8fCAoIXRoaXMuc2Vzc2lvbnNbc2lkXS5hY3RpdmUoKSkgPyAnY2FuY2VsJyA6IG51bGwsIHRleHQpO1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5zZXNzaW9uc1tzaWRdLnRlcm1pbmF0ZSgpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGRlbGV0ZSB0aGlzLmppZDJzZXNzaW9uW3RoaXMuc2Vzc2lvbnNbc2lkXS5wZWVyamlkXTtcbiAgICAgICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMuc2Vzc2lvbnNbc2lkXTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9IGVsc2UgaWYgKHRoaXMuc2Vzc2lvbnMuaGFzT3duUHJvcGVydHkoc2lkKSkge1xuICAgICAgICAgICAgICAgIGlmICh0aGlzLnNlc3Npb25zW3NpZF0uc3RhdGUgIT0gJ2VuZGVkJykge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnNlc3Npb25zW3NpZF0uc2VuZFRlcm1pbmF0ZShyZWFzb24gfHwgKCF0aGlzLnNlc3Npb25zW3NpZF0uYWN0aXZlKCkpID8gJ2NhbmNlbCcgOiBudWxsLCB0ZXh0KTtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5zZXNzaW9uc1tzaWRdLnRlcm1pbmF0ZSgpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5qaWQyc2Vzc2lvblt0aGlzLnNlc3Npb25zW3NpZF0ucGVlcmppZF07XG4gICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMuc2Vzc2lvbnNbc2lkXTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgLy8gVXNlZCB0byB0ZXJtaW5hdGUgYSBzZXNzaW9uIHdoZW4gYW4gdW5hdmFpbGFibGUgcHJlc2VuY2UgaXMgcmVjZWl2ZWQuXG4gICAgICAgIHRlcm1pbmF0ZUJ5SmlkOiBmdW5jdGlvbiAoamlkKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5qaWQyc2Vzc2lvbi5oYXNPd25Qcm9wZXJ0eShqaWQpKSB7XG4gICAgICAgICAgICAgICAgdmFyIHNlc3MgPSB0aGlzLmppZDJzZXNzaW9uW2ppZF07XG4gICAgICAgICAgICAgICAgaWYgKHNlc3MpIHtcbiAgICAgICAgICAgICAgICAgICAgc2Vzcy50ZXJtaW5hdGUoKTtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5sb2coJ3BlZXIgd2VudCBhd2F5IHNpbGVudGx5JywgamlkKTtcbiAgICAgICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMuc2Vzc2lvbnNbc2Vzcy5zaWRdO1xuICAgICAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5qaWQyc2Vzc2lvbltqaWRdO1xuICAgICAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdjYWxsdGVybWluYXRlZC5qaW5nbGUnLFxuICAgICAgICAgICAgICAgICAgICAgICAgW3Nlc3Muc2lkLCBqaWRdLCAnZ29uZScpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgdGVybWluYXRlUmVtb3RlQnlKaWQ6IGZ1bmN0aW9uIChqaWQsIHJlYXNvbikge1xuICAgICAgICAgICAgaWYgKHRoaXMuamlkMnNlc3Npb24uaGFzT3duUHJvcGVydHkoamlkKSkge1xuICAgICAgICAgICAgICAgIHZhciBzZXNzID0gdGhpcy5qaWQyc2Vzc2lvbltqaWRdO1xuICAgICAgICAgICAgICAgIGlmIChzZXNzKSB7XG4gICAgICAgICAgICAgICAgICAgIHNlc3Muc2VuZFRlcm1pbmF0ZShyZWFzb24gfHwgKCFzZXNzLmFjdGl2ZSgpKSA/ICdraWNrJyA6IG51bGwpO1xuICAgICAgICAgICAgICAgICAgICBzZXNzLnRlcm1pbmF0ZSgpO1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygndGVybWluYXRlIHBlZXIgd2l0aCBqaWQnLCBzZXNzLnNpZCwgamlkKTtcbiAgICAgICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMuc2Vzc2lvbnNbc2Vzcy5zaWRdO1xuICAgICAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5qaWQyc2Vzc2lvbltqaWRdO1xuICAgICAgICAgICAgICAgICAgICAkKGRvY3VtZW50KS50cmlnZ2VyKCdjYWxsdGVybWluYXRlZC5qaW5nbGUnLFxuICAgICAgICAgICAgICAgICAgICAgICAgW3Nlc3Muc2lkLCBqaWQsICdraWNrZWQnXSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBnZXRTdHVuQW5kVHVybkNyZWRlbnRpYWxzOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAvLyBnZXQgc3R1biBhbmQgdHVybiBjb25maWd1cmF0aW9uIGZyb20gc2VydmVyIHZpYSB4ZXAtMDIxNVxuICAgICAgICAgICAgLy8gdXNlcyB0aW1lLWxpbWl0ZWQgY3JlZGVudGlhbHMgYXMgZGVzY3JpYmVkIGluXG4gICAgICAgICAgICAvLyBodHRwOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9kcmFmdC11YmVydGktYmVoYXZlLXR1cm4tcmVzdC0wMFxuICAgICAgICAgICAgLy9cbiAgICAgICAgICAgIC8vIHNlZSBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL3Byb3NvZHktbW9kdWxlcy9zb3VyY2UvYnJvd3NlL21vZF90dXJuY3JlZGVudGlhbHMvbW9kX3R1cm5jcmVkZW50aWFscy5sdWFcbiAgICAgICAgICAgIC8vIGZvciBhIHByb3NvZHkgbW9kdWxlIHdoaWNoIGltcGxlbWVudHMgdGhpc1xuICAgICAgICAgICAgLy9cbiAgICAgICAgICAgIC8vIGN1cnJlbnRseSwgdGhpcyBkb2Vzbid0IHdvcmsgd2l0aCB1cGRhdGVJY2UgYW5kIHRoZXJlZm9yZSBjcmVkZW50aWFscyB3aXRoIGEgbG9uZ1xuICAgICAgICAgICAgLy8gdmFsaWRpdHkgaGF2ZSB0byBiZSBmZXRjaGVkIGJlZm9yZSBjcmVhdGluZyB0aGUgcGVlcmNvbm5lY3Rpb25cbiAgICAgICAgICAgIC8vIFRPRE86IGltcGxlbWVudCByZWZyZXNoIHZpYSB1cGRhdGVJY2UgYXMgZGVzY3JpYmVkIGluXG4gICAgICAgICAgICAvLyAgICAgIGh0dHBzOi8vY29kZS5nb29nbGUuY29tL3Avd2VicnRjL2lzc3Vlcy9kZXRhaWw/aWQ9MTY1MFxuICAgICAgICAgICAgdmFyIHNlbGYgPSB0aGlzO1xuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLnNlbmRJUShcbiAgICAgICAgICAgICAgICAkaXEoe3R5cGU6ICdnZXQnLCB0bzogdGhpcy5jb25uZWN0aW9uLmRvbWFpbn0pXG4gICAgICAgICAgICAgICAgICAgIC5jKCdzZXJ2aWNlcycsIHt4bWxuczogJ3Vybjp4bXBwOmV4dGRpc2NvOjEnfSkuYygnc2VydmljZScsIHtob3N0OiAndHVybi4nICsgdGhpcy5jb25uZWN0aW9uLmRvbWFpbn0pLFxuICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChyZXMpIHtcbiAgICAgICAgICAgICAgICAgICAgdmFyIGljZXNlcnZlcnMgPSBbXTtcbiAgICAgICAgICAgICAgICAgICAgJChyZXMpLmZpbmQoJz5zZXJ2aWNlcz5zZXJ2aWNlJykuZWFjaChmdW5jdGlvbiAoaWR4LCBlbCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgZWwgPSAkKGVsKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBkaWN0ID0ge307XG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgdHlwZSA9IGVsLmF0dHIoJ3R5cGUnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIHN3aXRjaCAodHlwZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhc2UgJ3N0dW4nOlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaWN0LnVybCA9ICdzdHVuOicgKyBlbC5hdHRyKCdob3N0Jyk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChlbC5hdHRyKCdwb3J0JykpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpY3QudXJsICs9ICc6JyArIGVsLmF0dHIoJ3BvcnQnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpY2VzZXJ2ZXJzLnB1c2goZGljdCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhc2UgJ3R1cm4nOlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhc2UgJ3R1cm5zJzpcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGljdC51cmwgPSB0eXBlICsgJzonO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWwuYXR0cigndXNlcm5hbWUnKSkgeyAvLyBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL3dlYnJ0Yy9pc3N1ZXMvZGV0YWlsP2lkPTE1MDhcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChuYXZpZ2F0b3IudXNlckFnZW50Lm1hdGNoKC9DaHJvbShlfGl1bSlcXC8oWzAtOV0rKVxcLi8pICYmIHBhcnNlSW50KG5hdmlnYXRvci51c2VyQWdlbnQubWF0Y2goL0Nocm9tKGV8aXVtKVxcLyhbMC05XSspXFwuLylbMl0sIDEwKSA8IDI4KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGljdC51cmwgKz0gZWwuYXR0cigndXNlcm5hbWUnKSArICdAJztcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGljdC51c2VybmFtZSA9IGVsLmF0dHIoJ3VzZXJuYW1lJyk7IC8vIG9ubHkgd29ya3MgaW4gTTI4XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGljdC51cmwgKz0gZWwuYXR0cignaG9zdCcpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWwuYXR0cigncG9ydCcpICYmIGVsLmF0dHIoJ3BvcnQnKSAhPSAnMzQ3OCcpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpY3QudXJsICs9ICc6JyArIGVsLmF0dHIoJ3BvcnQnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWwuYXR0cigndHJhbnNwb3J0JykgJiYgZWwuYXR0cigndHJhbnNwb3J0JykgIT0gJ3VkcCcpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpY3QudXJsICs9ICc/dHJhbnNwb3J0PScgKyBlbC5hdHRyKCd0cmFuc3BvcnQnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWwuYXR0cigncGFzc3dvcmQnKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGljdC5jcmVkZW50aWFsID0gZWwuYXR0cigncGFzc3dvcmQnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpY2VzZXJ2ZXJzLnB1c2goZGljdCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICAgICAgc2VsZi5pY2VfY29uZmlnLmljZVNlcnZlcnMgPSBpY2VzZXJ2ZXJzO1xuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgZnVuY3Rpb24gKGVycikge1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oJ2dldHRpbmcgdHVybiBjcmVkZW50aWFscyBmYWlsZWQnLCBlcnIpO1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLndhcm4oJ2lzIG1vZF90dXJuY3JlZGVudGlhbHMgb3Igc2ltaWxhciBpbnN0YWxsZWQ/Jyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIC8vIGltcGxlbWVudCBwdXNoP1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qKlxuICAgICAgICAgKiBQb3B1bGF0ZXMgdGhlIGxvZyBkYXRhXG4gICAgICAgICAqL1xuICAgICAgICBwb3B1bGF0ZURhdGE6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHZhciBkYXRhID0ge307XG4gICAgICAgICAgICBPYmplY3Qua2V5cyh0aGlzLnNlc3Npb25zKS5mb3JFYWNoKGZ1bmN0aW9uIChzaWQpIHtcbiAgICAgICAgICAgICAgICB2YXIgc2Vzc2lvbiA9IHRoaXMuc2Vzc2lvbnNbc2lkXTtcbiAgICAgICAgICAgICAgICBpZiAoc2Vzc2lvbi5wZWVyY29ubmVjdGlvbiAmJiBzZXNzaW9uLnBlZXJjb25uZWN0aW9uLnVwZGF0ZUxvZykge1xuICAgICAgICAgICAgICAgICAgICAvLyBGSVhNRTogc2hvdWxkIHByb2JhYmx5IGJlIGEgLmR1bXAgY2FsbFxuICAgICAgICAgICAgICAgICAgICBkYXRhW1wiamluZ2xlX1wiICsgc2Vzc2lvbi5zaWRdID0ge1xuICAgICAgICAgICAgICAgICAgICAgICAgdXBkYXRlTG9nOiBzZXNzaW9uLnBlZXJjb25uZWN0aW9uLnVwZGF0ZUxvZyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXRzOiBzZXNzaW9uLnBlZXJjb25uZWN0aW9uLnN0YXRzLFxuICAgICAgICAgICAgICAgICAgICAgICAgdXJsOiB3aW5kb3cubG9jYXRpb24uaHJlZlxuICAgICAgICAgICAgICAgICAgICB9O1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgcmV0dXJuIGRhdGE7XG4gICAgICAgIH1cbiAgICB9KTtcbn07XG5cbiIsIi8qIGdsb2JhbCBTdHJvcGhlICovXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uICgpIHtcblxuICAgIFN0cm9waGUuYWRkQ29ubmVjdGlvblBsdWdpbignbG9nZ2VyJywge1xuICAgICAgICAvLyBsb2dzIHJhdyBzdGFuemFzIGFuZCBtYWtlcyB0aGVtIGF2YWlsYWJsZSBmb3IgZG93bmxvYWQgYXMgSlNPTlxuICAgICAgICBjb25uZWN0aW9uOiBudWxsLFxuICAgICAgICBsb2c6IFtdLFxuICAgICAgICBpbml0OiBmdW5jdGlvbiAoY29ubikge1xuICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uID0gY29ubjtcbiAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5yYXdJbnB1dCA9IHRoaXMubG9nX2luY29taW5nLmJpbmQodGhpcyk7XG4gICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24ucmF3T3V0cHV0ID0gdGhpcy5sb2dfb3V0Z29pbmcuYmluZCh0aGlzKTtcbiAgICAgICAgfSxcbiAgICAgICAgbG9nX2luY29taW5nOiBmdW5jdGlvbiAoc3RhbnphKSB7XG4gICAgICAgICAgICB0aGlzLmxvZy5wdXNoKFtuZXcgRGF0ZSgpLmdldFRpbWUoKSwgJ2luY29taW5nJywgc3RhbnphXSk7XG4gICAgICAgIH0sXG4gICAgICAgIGxvZ19vdXRnb2luZzogZnVuY3Rpb24gKHN0YW56YSkge1xuICAgICAgICAgICAgdGhpcy5sb2cucHVzaChbbmV3IERhdGUoKS5nZXRUaW1lKCksICdvdXRnb2luZycsIHN0YW56YV0pO1xuICAgICAgICB9XG4gICAgfSk7XG59OyIsIi8qIGdsb2JhbCAkLCAkaXEsIGNvbmZpZywgY29ubmVjdGlvbiwgZm9jdXNNdWNKaWQsIGZvcmNlTXV0ZWQsXG4gICBzZXRBdWRpb011dGVkLCBTdHJvcGhlICovXG4vKipcbiAqIE1vZGVyYXRlIGNvbm5lY3Rpb24gcGx1Z2luLlxuICovXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChYTVBQKSB7XG4gICAgU3Ryb3BoZS5hZGRDb25uZWN0aW9uUGx1Z2luKCdtb2RlcmF0ZScsIHtcbiAgICAgICAgY29ubmVjdGlvbjogbnVsbCxcbiAgICAgICAgaW5pdDogZnVuY3Rpb24gKGNvbm4pIHtcbiAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbiA9IGNvbm47XG5cbiAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5hZGRIYW5kbGVyKHRoaXMub25NdXRlLmJpbmQodGhpcyksXG4gICAgICAgICAgICAgICAgJ2h0dHA6Ly9qaXRzaS5vcmcvaml0bWVldC9hdWRpbycsXG4gICAgICAgICAgICAgICAgJ2lxJyxcbiAgICAgICAgICAgICAgICAnc2V0JyxcbiAgICAgICAgICAgICAgICBudWxsLFxuICAgICAgICAgICAgICAgIG51bGwpO1xuICAgICAgICB9LFxuICAgICAgICBzZXRNdXRlOiBmdW5jdGlvbiAoamlkLCBtdXRlKSB7XG4gICAgICAgICAgICBjb25zb2xlLmluZm8oXCJzZXQgbXV0ZVwiLCBtdXRlKTtcbiAgICAgICAgICAgIHZhciBpcVRvRm9jdXMgPSAkaXEoe3RvOiBmb2N1c011Y0ppZCwgdHlwZTogJ3NldCd9KVxuICAgICAgICAgICAgICAgIC5jKCdtdXRlJywge1xuICAgICAgICAgICAgICAgICAgICB4bWxuczogJ2h0dHA6Ly9qaXRzaS5vcmcvaml0bWVldC9hdWRpbycsXG4gICAgICAgICAgICAgICAgICAgIGppZDogamlkXG4gICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAudChtdXRlLnRvU3RyaW5nKCkpXG4gICAgICAgICAgICAgICAgLnVwKCk7XG5cbiAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kSVEoXG4gICAgICAgICAgICAgICAgaXFUb0ZvY3VzLFxuICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChyZXN1bHQpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5sb2coJ3NldCBtdXRlJywgcmVzdWx0KTtcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChlcnJvcikge1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygnc2V0IG11dGUgZXJyb3InLCBlcnJvcik7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgIH0sXG4gICAgICAgIG9uTXV0ZTogZnVuY3Rpb24gKGlxKSB7XG4gICAgICAgICAgICB2YXIgZnJvbSA9IGlxLmdldEF0dHJpYnV0ZSgnZnJvbScpO1xuICAgICAgICAgICAgaWYgKGZyb20gIT09IGZvY3VzTXVjSmlkKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS53YXJuKFwiSWdub3JlZCBtdXRlIGZyb20gbm9uIGZvY3VzIHBlZXJcIik7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdmFyIG11dGUgPSAkKGlxKS5maW5kKCdtdXRlJyk7XG4gICAgICAgICAgICBpZiAobXV0ZS5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICB2YXIgZG9NdXRlQXVkaW8gPSBtdXRlLnRleHQoKSA9PT0gXCJ0cnVlXCI7XG4gICAgICAgICAgICAgICAgVUkuc2V0QXVkaW9NdXRlZChkb011dGVBdWRpbyk7XG4gICAgICAgICAgICAgICAgWE1QUC5mb3JjZU11dGVkID0gZG9NdXRlQXVkaW87XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfSxcbiAgICAgICAgZWplY3Q6IGZ1bmN0aW9uIChqaWQpIHtcbiAgICAgICAgICAgIC8vIFdlJ3JlIG5vdCB0aGUgZm9jdXMsIHNvIGNhbid0IHRlcm1pbmF0ZVxuICAgICAgICAgICAgLy9jb25uZWN0aW9uLmppbmdsZS50ZXJtaW5hdGVSZW1vdGVCeUppZChqaWQsICdraWNrJyk7XG4gICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uZW11Yy5raWNrKGppZCk7XG4gICAgICAgIH1cbiAgICB9KTtcbn0iLCIvKiBqc2hpbnQgLVcxMTcgKi9cbm1vZHVsZS5leHBvcnRzID0gZnVuY3Rpb24oKSB7XG4gICAgU3Ryb3BoZS5hZGRDb25uZWN0aW9uUGx1Z2luKCdyYXlvJyxcbiAgICAgICAge1xuICAgICAgICAgICAgUkFZT19YTUxOUzogJ3Vybjp4bXBwOnJheW86MScsXG4gICAgICAgICAgICBjb25uZWN0aW9uOiBudWxsLFxuICAgICAgICAgICAgaW5pdDogZnVuY3Rpb24gKGNvbm4pIHtcbiAgICAgICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24gPSBjb25uO1xuICAgICAgICAgICAgICAgIGlmICh0aGlzLmNvbm5lY3Rpb24uZGlzY28pIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5jb25uZWN0aW9uLmRpc2NvLmFkZEZlYXR1cmUoJ3Vybjp4bXBwOnJheW86Y2xpZW50OjEnKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uYWRkSGFuZGxlcihcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5vblJheW8uYmluZCh0aGlzKSwgdGhpcy5SQVlPX1hNTE5TLCAnaXEnLCAnc2V0JywgbnVsbCwgbnVsbCk7XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgb25SYXlvOiBmdW5jdGlvbiAoaXEpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmluZm8oXCJSYXlvIElRXCIsIGlxKTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBkaWFsOiBmdW5jdGlvbiAodG8sIGZyb20sIHJvb21OYW1lLCByb29tUGFzcykge1xuICAgICAgICAgICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgICAgICAgICB2YXIgcmVxID0gJGlxKFxuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiAnc2V0JyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRvOiBmb2N1c011Y0ppZFxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICByZXEuYygnZGlhbCcsXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHhtbG5zOiB0aGlzLlJBWU9fWE1MTlMsXG4gICAgICAgICAgICAgICAgICAgICAgICB0bzogdG8sXG4gICAgICAgICAgICAgICAgICAgICAgICBmcm9tOiBmcm9tXG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIHJlcS5jKCdoZWFkZXInLFxuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICBuYW1lOiAnSnZiUm9vbU5hbWUnLFxuICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWU6IHJvb21OYW1lXG4gICAgICAgICAgICAgICAgICAgIH0pLnVwKCk7XG5cbiAgICAgICAgICAgICAgICBpZiAocm9vbVBhc3MgIT09IG51bGwgJiYgcm9vbVBhc3MubGVuZ3RoKSB7XG5cbiAgICAgICAgICAgICAgICAgICAgcmVxLmMoJ2hlYWRlcicsXG4gICAgICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZTogJ0p2YlJvb21QYXNzd29yZCcsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWU6IHJvb21QYXNzXG4gICAgICAgICAgICAgICAgICAgICAgICB9KS51cCgpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHRoaXMuY29ubmVjdGlvbi5zZW5kSVEoXG4gICAgICAgICAgICAgICAgICAgIHJlcSxcbiAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gKHJlc3VsdCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgY29uc29sZS5pbmZvKCdEaWFsIHJlc3VsdCAnLCByZXN1bHQpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgcmVzb3VyY2UgPSAkKHJlc3VsdCkuZmluZCgncmVmJykuYXR0cigndXJpJyk7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmNhbGxfcmVzb3VyY2UgPSByZXNvdXJjZS5zdWJzdHIoJ3htcHA6Jy5sZW5ndGgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgY29uc29sZS5pbmZvKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcIlJlY2VpdmVkIGNhbGwgcmVzb3VyY2U6IFwiICsgdGhpcy5jYWxsX3Jlc291cmNlKTtcbiAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gKGVycm9yKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmluZm8oJ0RpYWwgZXJyb3IgJywgZXJyb3IpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBoYW5nX3VwOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgaWYgKCF0aGlzLmNhbGxfcmVzb3VyY2UpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS53YXJuKFwiTm8gY2FsbCBpbiBwcm9ncmVzc1wiKTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgICAgICAgICAgICB2YXIgcmVxID0gJGlxKFxuICAgICAgICAgICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiAnc2V0JyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRvOiB0aGlzLmNhbGxfcmVzb3VyY2VcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgcmVxLmMoJ2hhbmd1cCcsXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHhtbG5zOiB0aGlzLlJBWU9fWE1MTlNcbiAgICAgICAgICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgICAgICB0aGlzLmNvbm5lY3Rpb24uc2VuZElRKFxuICAgICAgICAgICAgICAgICAgICByZXEsXG4gICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChyZXN1bHQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnNvbGUuaW5mbygnSGFuZ3VwIHJlc3VsdCAnLCByZXN1bHQpO1xuICAgICAgICAgICAgICAgICAgICAgICAgc2VsZi5jYWxsX3Jlc291cmNlID0gbnVsbDtcbiAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gKGVycm9yKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmluZm8oJ0hhbmd1cCBlcnJvciAnLCBlcnJvcik7XG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxmLmNhbGxfcmVzb3VyY2UgPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICk7XG59O1xuIiwiLyoqXG4gKiBTdHJvcGhlIGxvZ2dlciBpbXBsZW1lbnRhdGlvbi4gTG9ncyBmcm9tIGxldmVsIFdBUk4gYW5kIGFib3ZlLlxuICovXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uICgpIHtcblxuICAgIFN0cm9waGUubG9nID0gZnVuY3Rpb24gKGxldmVsLCBtc2cpIHtcbiAgICAgICAgc3dpdGNoIChsZXZlbCkge1xuICAgICAgICAgICAgY2FzZSBTdHJvcGhlLkxvZ0xldmVsLldBUk46XG4gICAgICAgICAgICAgICAgY29uc29sZS53YXJuKFwiU3Ryb3BoZTogXCIgKyBtc2cpO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgY2FzZSBTdHJvcGhlLkxvZ0xldmVsLkVSUk9SOlxuICAgICAgICAgICAgY2FzZSBTdHJvcGhlLkxvZ0xldmVsLkZBVEFMOlxuICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJTdHJvcGhlOiBcIiArIG1zZyk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgU3Ryb3BoZS5nZXRTdGF0dXNTdHJpbmcgPSBmdW5jdGlvbiAoc3RhdHVzKSB7XG4gICAgICAgIHN3aXRjaCAoc3RhdHVzKSB7XG4gICAgICAgICAgICBjYXNlIFN0cm9waGUuU3RhdHVzLkVSUk9SOlxuICAgICAgICAgICAgICAgIHJldHVybiBcIkVSUk9SXCI7XG4gICAgICAgICAgICBjYXNlIFN0cm9waGUuU3RhdHVzLkNPTk5FQ1RJTkc6XG4gICAgICAgICAgICAgICAgcmV0dXJuIFwiQ09OTkVDVElOR1wiO1xuICAgICAgICAgICAgY2FzZSBTdHJvcGhlLlN0YXR1cy5DT05ORkFJTDpcbiAgICAgICAgICAgICAgICByZXR1cm4gXCJDT05ORkFJTFwiO1xuICAgICAgICAgICAgY2FzZSBTdHJvcGhlLlN0YXR1cy5BVVRIRU5USUNBVElORzpcbiAgICAgICAgICAgICAgICByZXR1cm4gXCJBVVRIRU5USUNBVElOR1wiO1xuICAgICAgICAgICAgY2FzZSBTdHJvcGhlLlN0YXR1cy5BVVRIRkFJTDpcbiAgICAgICAgICAgICAgICByZXR1cm4gXCJBVVRIRkFJTFwiO1xuICAgICAgICAgICAgY2FzZSBTdHJvcGhlLlN0YXR1cy5DT05ORUNURUQ6XG4gICAgICAgICAgICAgICAgcmV0dXJuIFwiQ09OTkVDVEVEXCI7XG4gICAgICAgICAgICBjYXNlIFN0cm9waGUuU3RhdHVzLkRJU0NPTk5FQ1RFRDpcbiAgICAgICAgICAgICAgICByZXR1cm4gXCJESVNDT05ORUNURURcIjtcbiAgICAgICAgICAgIGNhc2UgU3Ryb3BoZS5TdGF0dXMuRElTQ09OTkVDVElORzpcbiAgICAgICAgICAgICAgICByZXR1cm4gXCJESVNDT05ORUNUSU5HXCI7XG4gICAgICAgICAgICBjYXNlIFN0cm9waGUuU3RhdHVzLkFUVEFDSEVEOlxuICAgICAgICAgICAgICAgIHJldHVybiBcIkFUVEFDSEVEXCI7XG4gICAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgICAgIHJldHVybiBcInVua25vd25cIjtcbiAgICAgICAgfVxuICAgIH07XG59O1xuIiwidmFyIE1vZGVyYXRvciA9IHJlcXVpcmUoXCIuL21vZGVyYXRvclwiKTtcbnZhciBFdmVudEVtaXR0ZXIgPSByZXF1aXJlKFwiZXZlbnRzXCIpO1xudmFyIFJlY29yZGluZyA9IHJlcXVpcmUoXCIuL3JlY29yZGluZ1wiKTtcbnZhciBTRFAgPSByZXF1aXJlKFwiLi9TRFBcIik7XG5cbnZhciBldmVudEVtaXR0ZXIgPSBuZXcgRXZlbnRFbWl0dGVyKCk7XG52YXIgY29ubmVjdGlvbiA9IG51bGw7XG52YXIgYXV0aGVudGljYXRlZFVzZXIgPSBmYWxzZTtcbnZhciBhY3RpdmVjYWxsID0gbnVsbDtcblxuZnVuY3Rpb24gY29ubmVjdChqaWQsIHBhc3N3b3JkLCB1aUNyZWRlbnRpYWxzKSB7XG4gICAgdmFyIGJvc2hcbiAgICAgICAgPSB1aUNyZWRlbnRpYWxzLmJvc2ggfHwgY29uZmlnLmJvc2ggfHwgJy9odHRwLWJpbmQnO1xuICAgIGNvbm5lY3Rpb24gPSBuZXcgU3Ryb3BoZS5Db25uZWN0aW9uKGJvc2gpO1xuICAgIE1vZGVyYXRvci5zZXRDb25uZWN0aW9uKGNvbm5lY3Rpb24pO1xuXG4gICAgdmFyIHNldHRpbmdzID0gVUkuZ2V0U2V0dGluZ3MoKTtcbiAgICB2YXIgZW1haWwgPSBzZXR0aW5ncy5lbWFpbDtcbiAgICB2YXIgZGlzcGxheU5hbWUgPSBzZXR0aW5ncy5kaXNwbGF5TmFtZTtcbiAgICBpZihlbWFpbCkge1xuICAgICAgICBjb25uZWN0aW9uLmVtdWMuYWRkRW1haWxUb1ByZXNlbmNlKGVtYWlsKTtcbiAgICB9IGVsc2Uge1xuICAgICAgICBjb25uZWN0aW9uLmVtdWMuYWRkVXNlcklkVG9QcmVzZW5jZShzZXR0aW5ncy51aWQpO1xuICAgIH1cbiAgICBpZihkaXNwbGF5TmFtZSkge1xuICAgICAgICBjb25uZWN0aW9uLmVtdWMuYWRkRGlzcGxheU5hbWVUb1ByZXNlbmNlKGRpc3BsYXlOYW1lKTtcbiAgICB9XG5cbiAgICBpZiAoY29ubmVjdGlvbi5kaXNjbykge1xuICAgICAgICAvLyBmb3IgY2hyb21lLCBhZGQgbXVsdGlzdHJlYW0gY2FwXG4gICAgfVxuICAgIGNvbm5lY3Rpb24uamluZ2xlLnBjX2NvbnN0cmFpbnRzID0gUlRDLmdldFBDQ29uc3RyYWludHMoKTtcbiAgICBpZiAoY29uZmlnLnVzZUlQdjYpIHtcbiAgICAgICAgLy8gaHR0cHM6Ly9jb2RlLmdvb2dsZS5jb20vcC93ZWJydGMvaXNzdWVzL2RldGFpbD9pZD0yODI4XG4gICAgICAgIGlmICghY29ubmVjdGlvbi5qaW5nbGUucGNfY29uc3RyYWludHMub3B0aW9uYWwpXG4gICAgICAgICAgICBjb25uZWN0aW9uLmppbmdsZS5wY19jb25zdHJhaW50cy5vcHRpb25hbCA9IFtdO1xuICAgICAgICBjb25uZWN0aW9uLmppbmdsZS5wY19jb25zdHJhaW50cy5vcHRpb25hbC5wdXNoKHtnb29nSVB2NjogdHJ1ZX0pO1xuICAgIH1cblxuICAgIGlmKCFwYXNzd29yZClcbiAgICAgICAgcGFzc3dvcmQgPSB1aUNyZWRlbnRpYWxzLnBhc3N3b3JkO1xuXG4gICAgdmFyIGFub255bW91c0Nvbm5lY3Rpb25GYWlsZWQgPSBmYWxzZTtcbiAgICBjb25uZWN0aW9uLmNvbm5lY3QoamlkLCBwYXNzd29yZCwgZnVuY3Rpb24gKHN0YXR1cywgbXNnKSB7XG4gICAgICAgIGNvbnNvbGUubG9nKCdTdHJvcGhlIHN0YXR1cyBjaGFuZ2VkIHRvJyxcbiAgICAgICAgICAgIFN0cm9waGUuZ2V0U3RhdHVzU3RyaW5nKHN0YXR1cykpO1xuICAgICAgICBpZiAoc3RhdHVzID09PSBTdHJvcGhlLlN0YXR1cy5DT05ORUNURUQpIHtcbiAgICAgICAgICAgIGlmIChjb25maWcudXNlU3R1blR1cm4pIHtcbiAgICAgICAgICAgICAgICBjb25uZWN0aW9uLmppbmdsZS5nZXRTdHVuQW5kVHVybkNyZWRlbnRpYWxzKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBVSS5kaXNhYmxlQ29ubmVjdCgpO1xuXG4gICAgICAgICAgICBjb25zb2xlLmluZm8oXCJNeSBKYWJiZXIgSUQ6IFwiICsgY29ubmVjdGlvbi5qaWQpO1xuXG4gICAgICAgICAgICBpZihwYXNzd29yZClcbiAgICAgICAgICAgICAgICBhdXRoZW50aWNhdGVkVXNlciA9IHRydWU7XG4gICAgICAgICAgICBtYXliZURvSm9pbigpO1xuICAgICAgICB9IGVsc2UgaWYgKHN0YXR1cyA9PT0gU3Ryb3BoZS5TdGF0dXMuQ09OTkZBSUwpIHtcbiAgICAgICAgICAgIGlmKG1zZyA9PT0gJ3gtc3Ryb3BoZS1iYWQtbm9uLWFub24tamlkJykge1xuICAgICAgICAgICAgICAgIGFub255bW91c0Nvbm5lY3Rpb25GYWlsZWQgPSB0cnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9IGVsc2UgaWYgKHN0YXR1cyA9PT0gU3Ryb3BoZS5TdGF0dXMuRElTQ09OTkVDVEVEKSB7XG4gICAgICAgICAgICBpZihhbm9ueW1vdXNDb25uZWN0aW9uRmFpbGVkKSB7XG4gICAgICAgICAgICAgICAgLy8gcHJvbXB0IHVzZXIgZm9yIHVzZXJuYW1lIGFuZCBwYXNzd29yZFxuICAgICAgICAgICAgICAgIFhNUFAucHJvbXB0TG9naW4oKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChzdGF0dXMgPT09IFN0cm9waGUuU3RhdHVzLkFVVEhGQUlMKSB7XG4gICAgICAgICAgICAvLyB3cm9uZyBwYXNzd29yZCBvciB1c2VybmFtZSwgcHJvbXB0IHVzZXJcbiAgICAgICAgICAgIFhNUFAucHJvbXB0TG9naW4oKTtcblxuICAgICAgICB9XG4gICAgfSk7XG59XG5cblxuXG5mdW5jdGlvbiBtYXliZURvSm9pbigpIHtcbiAgICBpZiAoY29ubmVjdGlvbiAmJiBjb25uZWN0aW9uLmNvbm5lY3RlZCAmJlxuICAgICAgICBTdHJvcGhlLmdldFJlc291cmNlRnJvbUppZChjb25uZWN0aW9uLmppZClcbiAgICAgICAgJiYgKFJUQy5sb2NhbEF1ZGlvIHx8IFJUQy5sb2NhbFZpZGVvKSkge1xuICAgICAgICAvLyAuY29ubmVjdGVkIGlzIHRydWUgd2hpbGUgY29ubmVjdGluZz9cbiAgICAgICAgZG9Kb2luKCk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBkb0pvaW4oKSB7XG4gICAgdmFyIHJvb21OYW1lID0gVUkuZ2VuZXJhdGVSb29tTmFtZSgpO1xuXG4gICAgTW9kZXJhdG9yLmFsbG9jYXRlQ29uZmVyZW5jZUZvY3VzKFxuICAgICAgICByb29tTmFtZSwgVUkuY2hlY2tGb3JOaWNrbmFtZUFuZEpvaW4pO1xufVxuXG5mdW5jdGlvbiBpbml0U3Ryb3BoZVBsdWdpbnMoKVxue1xuICAgIHJlcXVpcmUoXCIuL3N0cm9waGUuZW11Y1wiKShYTVBQLCBldmVudEVtaXR0ZXIpO1xuICAgIHJlcXVpcmUoXCIuL3N0cm9waGUuamluZ2xlXCIpKCk7XG4gICAgcmVxdWlyZShcIi4vc3Ryb3BoZS5tb2RlcmF0ZVwiKShYTVBQKTtcbiAgICByZXF1aXJlKFwiLi9zdHJvcGhlLnV0aWxcIikoKTtcbiAgICByZXF1aXJlKFwiLi9zdHJvcGhlLnJheW9cIikoKTtcbiAgICByZXF1aXJlKFwiLi9zdHJvcGhlLmxvZ2dlclwiKSgpO1xufVxuXG5mdW5jdGlvbiByZWdpc3Rlckxpc3RlbmVycygpIHtcbiAgICBSVEMuYWRkU3RyZWFtTGlzdGVuZXIobWF5YmVEb0pvaW4sXG4gICAgICAgIFN0cmVhbUV2ZW50VHlwZXMuRVZFTlRfVFlQRV9MT0NBTF9DUkVBVEVEKTtcbn1cblxuZnVuY3Rpb24gc2V0dXBFdmVudHMoKSB7XG4gICAgJCh3aW5kb3cpLmJpbmQoJ2JlZm9yZXVubG9hZCcsIGZ1bmN0aW9uICgpIHtcbiAgICAgICAgaWYgKGNvbm5lY3Rpb24gJiYgY29ubmVjdGlvbi5jb25uZWN0ZWQpIHtcbiAgICAgICAgICAgIC8vIGVuc3VyZSBzaWdub3V0XG4gICAgICAgICAgICAkLmFqYXgoe1xuICAgICAgICAgICAgICAgIHR5cGU6ICdQT1NUJyxcbiAgICAgICAgICAgICAgICB1cmw6IGNvbmZpZy5ib3NoLFxuICAgICAgICAgICAgICAgIGFzeW5jOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBjYWNoZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgY29udGVudFR5cGU6ICdhcHBsaWNhdGlvbi94bWwnLFxuICAgICAgICAgICAgICAgIGRhdGE6IFwiPGJvZHkgcmlkPSdcIiArIChjb25uZWN0aW9uLnJpZCB8fCBjb25uZWN0aW9uLl9wcm90by5yaWQpXG4gICAgICAgICAgICAgICAgICAgICsgXCInIHhtbG5zPSdodHRwOi8vamFiYmVyLm9yZy9wcm90b2NvbC9odHRwYmluZCcgc2lkPSdcIlxuICAgICAgICAgICAgICAgICAgICArIChjb25uZWN0aW9uLnNpZCB8fCBjb25uZWN0aW9uLl9wcm90by5zaWQpXG4gICAgICAgICAgICAgICAgICAgICsgXCInIHR5cGU9J3Rlcm1pbmF0ZSc+XCIgK1xuICAgICAgICAgICAgICAgICAgICBcIjxwcmVzZW5jZSB4bWxucz0namFiYmVyOmNsaWVudCcgdHlwZT0ndW5hdmFpbGFibGUnLz5cIiArXG4gICAgICAgICAgICAgICAgICAgIFwiPC9ib2R5PlwiLFxuICAgICAgICAgICAgICAgIHN1Y2Nlc3M6IGZ1bmN0aW9uIChkYXRhKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCdzaWduZWQgb3V0Jyk7XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKGRhdGEpO1xuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgZXJyb3I6IGZ1bmN0aW9uIChYTUxIdHRwUmVxdWVzdCwgdGV4dFN0YXR1cywgZXJyb3JUaHJvd24pIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5sb2coJ3NpZ25vdXQgZXJyb3InLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRleHRTdGF0dXMgKyAnICgnICsgZXJyb3JUaHJvd24gKyAnKScpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIFhNUFAuZGlzcG9zZUNvbmZlcmVuY2UodHJ1ZSk7XG4gICAgfSk7XG59XG5cbnZhciBYTVBQID0ge1xuICAgIHNlc3Npb25UZXJtaW5hdGVkOiBmYWxzZSxcbiAgICAvKipcbiAgICAgKiBSZW1lbWJlcnMgaWYgd2Ugd2VyZSBtdXRlZCBieSB0aGUgZm9jdXMuXG4gICAgICogQHR5cGUge2Jvb2xlYW59XG4gICAgICovXG4gICAgZm9yY2VNdXRlZDogZmFsc2UsXG4gICAgc3RhcnQ6IGZ1bmN0aW9uICh1aUNyZWRlbnRpYWxzKSB7XG4gICAgICAgIHNldHVwRXZlbnRzKCk7XG4gICAgICAgIGluaXRTdHJvcGhlUGx1Z2lucygpO1xuICAgICAgICByZWdpc3Rlckxpc3RlbmVycygpO1xuICAgICAgICBNb2RlcmF0b3IuaW5pdCgpO1xuICAgICAgICB2YXIgamlkID0gdWlDcmVkZW50aWFscy5qaWQgfHxcbiAgICAgICAgICAgIGNvbmZpZy5ob3N0cy5hbm9ueW1vdXNkb21haW4gfHxcbiAgICAgICAgICAgIGNvbmZpZy5ob3N0cy5kb21haW4gfHxcbiAgICAgICAgICAgIHdpbmRvdy5sb2NhdGlvbi5ob3N0bmFtZTtcbiAgICAgICAgY29ubmVjdChqaWQsIG51bGwsIHVpQ3JlZGVudGlhbHMpO1xuICAgIH0sXG4gICAgcHJvbXB0TG9naW46IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgVUkuc2hvd0xvZ2luUG9wdXAoY29ubmVjdCk7XG4gICAgfSxcbiAgICBqb2luUm9vb206IGZ1bmN0aW9uKHJvb21OYW1lLCB1c2VOaWNrcywgbmljaylcbiAgICB7XG4gICAgICAgIHZhciByb29tamlkO1xuICAgICAgICByb29tamlkID0gcm9vbU5hbWU7XG5cbiAgICAgICAgaWYgKHVzZU5pY2tzKSB7XG4gICAgICAgICAgICBpZiAobmljaykge1xuICAgICAgICAgICAgICAgIHJvb21qaWQgKz0gJy8nICsgbmljaztcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgcm9vbWppZCArPSAnLycgKyBTdHJvcGhlLmdldE5vZGVGcm9tSmlkKGNvbm5lY3Rpb24uamlkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcblxuICAgICAgICAgICAgdmFyIHRtcEppZCA9IFN0cm9waGUuZ2V0Tm9kZUZyb21KaWQoY29ubmVjdGlvbi5qaWQpO1xuXG4gICAgICAgICAgICBpZighYXV0aGVudGljYXRlZFVzZXIpXG4gICAgICAgICAgICAgICAgdG1wSmlkID0gdG1wSmlkLnN1YnN0cigwLCA4KTtcblxuICAgICAgICAgICAgcm9vbWppZCArPSAnLycgKyB0bXBKaWQ7XG4gICAgICAgIH1cbiAgICAgICAgY29ubmVjdGlvbi5lbXVjLmRvSm9pbihyb29tamlkKTtcbiAgICB9LFxuICAgIG15SmlkOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGlmKCFjb25uZWN0aW9uKVxuICAgICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIHJldHVybiBjb25uZWN0aW9uLmVtdWMubXlyb29tamlkO1xuICAgIH0sXG4gICAgbXlSZXNvdXJjZTogZnVuY3Rpb24gKCkge1xuICAgICAgICBpZighY29ubmVjdGlvbiB8fCAhIGNvbm5lY3Rpb24uZW11Yy5teXJvb21qaWQpXG4gICAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgcmV0dXJuIFN0cm9waGUuZ2V0UmVzb3VyY2VGcm9tSmlkKGNvbm5lY3Rpb24uZW11Yy5teXJvb21qaWQpO1xuICAgIH0sXG4gICAgZGlzcG9zZUNvbmZlcmVuY2U6IGZ1bmN0aW9uIChvblVubG9hZCkge1xuICAgICAgICBldmVudEVtaXR0ZXIuZW1pdChYTVBQRXZlbnRzLkRJU1BPU0VfQ09ORkVSRU5DRSwgb25VbmxvYWQpO1xuICAgICAgICB2YXIgaGFuZGxlciA9IGFjdGl2ZWNhbGw7XG4gICAgICAgIGlmIChoYW5kbGVyICYmIGhhbmRsZXIucGVlcmNvbm5lY3Rpb24pIHtcbiAgICAgICAgICAgIC8vIEZJWE1FOiBwcm9iYWJseSByZW1vdmluZyBzdHJlYW1zIGlzIG5vdCByZXF1aXJlZCBhbmQgY2xvc2UoKSBzaG91bGRcbiAgICAgICAgICAgIC8vIGJlIGVub3VnaFxuICAgICAgICAgICAgaWYgKFJUQy5sb2NhbEF1ZGlvKSB7XG4gICAgICAgICAgICAgICAgaGFuZGxlci5wZWVyY29ubmVjdGlvbi5yZW1vdmVTdHJlYW0oUlRDLmxvY2FsQXVkaW8uZ2V0T3JpZ2luYWxTdHJlYW0oKSwgb25VbmxvYWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKFJUQy5sb2NhbFZpZGVvKSB7XG4gICAgICAgICAgICAgICAgaGFuZGxlci5wZWVyY29ubmVjdGlvbi5yZW1vdmVTdHJlYW0oUlRDLmxvY2FsVmlkZW8uZ2V0T3JpZ2luYWxTdHJlYW0oKSwgb25VbmxvYWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaGFuZGxlci5wZWVyY29ubmVjdGlvbi5jbG9zZSgpO1xuICAgICAgICB9XG4gICAgICAgIGFjdGl2ZWNhbGwgPSBudWxsO1xuICAgICAgICBpZighb25VbmxvYWQpXG4gICAgICAgIHtcbiAgICAgICAgICAgIHRoaXMuc2Vzc2lvblRlcm1pbmF0ZWQgPSB0cnVlO1xuICAgICAgICAgICAgY29ubmVjdGlvbi5lbXVjLmRvTGVhdmUoKTtcbiAgICAgICAgfVxuICAgIH0sXG4gICAgYWRkTGlzdGVuZXI6IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKVxuICAgIHtcbiAgICAgICAgZXZlbnRFbWl0dGVyLm9uKHR5cGUsIGxpc3RlbmVyKTtcbiAgICB9LFxuICAgIHJlbW92ZUxpc3RlbmVyOiBmdW5jdGlvbiAodHlwZSwgbGlzdGVuZXIpIHtcbiAgICAgICAgZXZlbnRFbWl0dGVyLnJlbW92ZUxpc3RlbmVyKHR5cGUsIGxpc3RlbmVyKTtcbiAgICB9LFxuICAgIGFsbG9jYXRlQ29uZmVyZW5jZUZvY3VzOiBmdW5jdGlvbihyb29tTmFtZSwgY2FsbGJhY2spIHtcbiAgICAgICAgTW9kZXJhdG9yLmFsbG9jYXRlQ29uZmVyZW5jZUZvY3VzKHJvb21OYW1lLCBjYWxsYmFjayk7XG4gICAgfSxcbiAgICBpc01vZGVyYXRvcjogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gTW9kZXJhdG9yLmlzTW9kZXJhdG9yKCk7XG4gICAgfSxcbiAgICBpc1NpcEdhdGV3YXlFbmFibGVkOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBNb2RlcmF0b3IuaXNTaXBHYXRld2F5RW5hYmxlZCgpO1xuICAgIH0sXG4gICAgaXNFeHRlcm5hbEF1dGhFbmFibGVkOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBNb2RlcmF0b3IuaXNFeHRlcm5hbEF1dGhFbmFibGVkKCk7XG4gICAgfSxcbiAgICBzd2l0Y2hTdHJlYW1zOiBmdW5jdGlvbiAoc3RyZWFtLCBvbGRTdHJlYW0sIGNhbGxiYWNrKSB7XG4gICAgICAgIGlmIChhY3RpdmVjYWxsKSB7XG4gICAgICAgICAgICAvLyBGSVhNRTogd2lsbCBibG9jayBzd2l0Y2hJblByb2dyZXNzIG9uIHRydWUgdmFsdWUgaW4gY2FzZSBvZiBleGNlcHRpb25cbiAgICAgICAgICAgIGFjdGl2ZWNhbGwuc3dpdGNoU3RyZWFtcyhzdHJlYW0sIG9sZFN0cmVhbSwgY2FsbGJhY2spO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gV2UgYXJlIGRvbmUgaW1tZWRpYXRlbHlcbiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoXCJObyBjb25mZXJlbmNlIGhhbmRsZXJcIik7XG4gICAgICAgICAgICBVSS5tZXNzYWdlSGFuZGxlci5zaG93RXJyb3IoJ0Vycm9yJyxcbiAgICAgICAgICAgICAgICAnVW5hYmxlIHRvIHN3aXRjaCB2aWRlbyBzdHJlYW0uJyk7XG4gICAgICAgICAgICBjYWxsYmFjaygpO1xuICAgICAgICB9XG4gICAgfSxcbiAgICBzZXRWaWRlb011dGU6IGZ1bmN0aW9uIChtdXRlLCBjYWxsYmFjaywgb3B0aW9ucykge1xuICAgICAgIGlmKGFjdGl2ZWNhbGwgJiYgY29ubmVjdGlvbiAmJiBSVEMubG9jYWxWaWRlbylcbiAgICAgICB7XG4gICAgICAgICAgIGFjdGl2ZWNhbGwuc2V0VmlkZW9NdXRlKG11dGUsIGNhbGxiYWNrLCBvcHRpb25zKTtcbiAgICAgICB9XG4gICAgfSxcbiAgICBzZXRBdWRpb011dGU6IGZ1bmN0aW9uIChtdXRlLCBjYWxsYmFjaykge1xuICAgICAgICBpZiAoIShjb25uZWN0aW9uICYmIFJUQy5sb2NhbEF1ZGlvKSkge1xuICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICB9XG5cblxuICAgICAgICBpZiAodGhpcy5mb3JjZU11dGVkICYmICFtdXRlKSB7XG4gICAgICAgICAgICBjb25zb2xlLmluZm8oXCJBc2tpbmcgZm9jdXMgZm9yIHVubXV0ZVwiKTtcbiAgICAgICAgICAgIGNvbm5lY3Rpb24ubW9kZXJhdGUuc2V0TXV0ZShjb25uZWN0aW9uLmVtdWMubXlyb29tamlkLCBtdXRlKTtcbiAgICAgICAgICAgIC8vIEZJWE1FOiB3YWl0IGZvciByZXN1bHQgYmVmb3JlIHJlc2V0dGluZyBtdXRlZCBzdGF0dXNcbiAgICAgICAgICAgIHRoaXMuZm9yY2VNdXRlZCA9IGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKG11dGUgPT0gUlRDLmxvY2FsQXVkaW8uaXNNdXRlZCgpKSB7XG4gICAgICAgICAgICAvLyBOb3RoaW5nIHRvIGRvXG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIEl0IGlzIG5vdCBjbGVhciB3aGF0IGlzIHRoZSByaWdodCB3YXkgdG8gaGFuZGxlIG11bHRpcGxlIHRyYWNrcy5cbiAgICAgICAgLy8gU28gYXQgbGVhc3QgbWFrZSBzdXJlIHRoYXQgdGhleSBhcmUgYWxsIG11dGVkIG9yIGFsbCB1bm11dGVkIGFuZFxuICAgICAgICAvLyB0aGF0IHdlIHNlbmQgcHJlc2VuY2UganVzdCBvbmNlLlxuICAgICAgICBSVEMubG9jYWxBdWRpby5tdXRlKCk7XG4gICAgICAgIC8vIGlzTXV0ZWQgaXMgdGhlIG9wcG9zaXRlIG9mIGF1ZGlvRW5hYmxlZFxuICAgICAgICBjb25uZWN0aW9uLmVtdWMuYWRkQXVkaW9JbmZvVG9QcmVzZW5jZShtdXRlKTtcbiAgICAgICAgY29ubmVjdGlvbi5lbXVjLnNlbmRQcmVzZW5jZSgpO1xuICAgICAgICBjYWxsYmFjaygpO1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9LFxuICAgIC8vIFJlYWxseSBtdXRlIHZpZGVvLCBpLmUuIGRvbnQgZXZlbiBzZW5kIGJsYWNrIGZyYW1lc1xuICAgIG11dGVWaWRlbzogZnVuY3Rpb24gKHBjLCB1bm11dGUpIHtcbiAgICAgICAgLy8gRklYTUU6IHRoaXMgcHJvYmFibHkgbmVlZHMgYW5vdGhlciBvZiB0aG9zZSBsb3ZlbHkgc3RhdGUgc2FmZWd1YXJkcy4uLlxuICAgICAgICAvLyB3aGljaCBjaGVja3MgZm9yIGljZWNvbm4gPT0gY29ubmVjdGVkIGFuZCBzaWdzdGF0ZSA9PSBzdGFibGVcbiAgICAgICAgcGMuc2V0UmVtb3RlRGVzY3JpcHRpb24ocGMucmVtb3RlRGVzY3JpcHRpb24sXG4gICAgICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgcGMuY3JlYXRlQW5zd2VyKFxuICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbiAoYW5zd2VyKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgc2RwID0gbmV3IFNEUChhbnN3ZXIuc2RwKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChzZHAubWVkaWEubGVuZ3RoID4gMSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICh1bm11dGUpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkcC5tZWRpYVsxXSA9IHNkcC5tZWRpYVsxXS5yZXBsYWNlKCdhPXJlY3Zvbmx5JywgJ2E9c2VuZHJlY3YnKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkcC5tZWRpYVsxXSA9IHNkcC5tZWRpYVsxXS5yZXBsYWNlKCdhPXNlbmRyZWN2JywgJ2E9cmVjdm9ubHknKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZHAucmF3ID0gc2RwLnNlc3Npb24gKyBzZHAubWVkaWEuam9pbignJyk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYW5zd2VyLnNkcCA9IHNkcC5yYXc7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICBwYy5zZXRMb2NhbERlc2NyaXB0aW9uKGFuc3dlcixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCdtdXRlIFNMRCBvaycpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24gKGVycm9yKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCdtdXRlIFNMRCBlcnJvcicpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBVSS5tZXNzYWdlSGFuZGxlci5zaG93RXJyb3IoJ0Vycm9yJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnT29wcyEgU29tZXRoaW5nIHdlbnQgd3JvbmcgYW5kIHdlIGZhaWxlZCB0byAnICtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnbXV0ZSEgKFNMRCBGYWlsdXJlKScpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uIChlcnJvcikge1xuICAgICAgICAgICAgICAgICAgICAgICAgY29uc29sZS5sb2coZXJyb3IpO1xuICAgICAgICAgICAgICAgICAgICAgICAgVUkubWVzc2FnZUhhbmRsZXIuc2hvd0Vycm9yKCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGZ1bmN0aW9uIChlcnJvcikge1xuICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCdtdXRlVmlkZW8gU1JEIGVycm9yJyk7XG4gICAgICAgICAgICAgICAgVUkubWVzc2FnZUhhbmRsZXIuc2hvd0Vycm9yKCdFcnJvcicsXG4gICAgICAgICAgICAgICAgICAgICAgICAnT29wcyEgU29tZXRoaW5nIHdlbnQgd3JvbmcgYW5kIHdlIGZhaWxlZCB0byBzdG9wIHZpZGVvIScgK1xuICAgICAgICAgICAgICAgICAgICAgICAgJyhTUkQgRmFpbHVyZSknKTtcblxuICAgICAgICAgICAgfVxuICAgICAgICApO1xuICAgIH0sXG4gICAgdG9nZ2xlUmVjb3JkaW5nOiBmdW5jdGlvbiAodG9rZW5FbXB0eUNhbGxiYWNrLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0aW5nQ2FsbGJhY2ssIHN0YXJ0ZWRDYWxsYmFjaykge1xuICAgICAgICBSZWNvcmRpbmcudG9nZ2xlUmVjb3JkaW5nKHRva2VuRW1wdHlDYWxsYmFjayxcbiAgICAgICAgICAgIHN0YXJ0aW5nQ2FsbGJhY2ssIHN0YXJ0ZWRDYWxsYmFjayk7XG4gICAgfSxcbiAgICBhZGRUb1ByZXNlbmNlOiBmdW5jdGlvbiAobmFtZSwgdmFsdWUsIGRvbnRTZW5kKSB7XG4gICAgICAgIHN3aXRjaCAobmFtZSlcbiAgICAgICAge1xuICAgICAgICAgICAgY2FzZSBcImRpc3BsYXlOYW1lXCI6XG4gICAgICAgICAgICAgICAgY29ubmVjdGlvbi5lbXVjLmFkZERpc3BsYXlOYW1lVG9QcmVzZW5jZSh2YWx1ZSk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBjYXNlIFwiZXRoZXJwYWRcIjpcbiAgICAgICAgICAgICAgICBjb25uZWN0aW9uLmVtdWMuYWRkRXRoZXJwYWRUb1ByZXNlbmNlKHZhbHVlKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIGNhc2UgXCJwcmV6aVwiOlxuICAgICAgICAgICAgICAgIGNvbm5lY3Rpb24uZW11Yy5hZGRQcmV6aVRvUHJlc2VuY2UodmFsdWUsIDApO1xuICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgY2FzZSBcInByZXppU2xpZGVcIjpcbiAgICAgICAgICAgICAgICBjb25uZWN0aW9uLmVtdWMuYWRkQ3VycmVudFNsaWRlVG9QcmVzZW5jZSh2YWx1ZSk7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICBjYXNlIFwiY29ubmVjdGlvblF1YWxpdHlcIjpcbiAgICAgICAgICAgICAgICBjb25uZWN0aW9uLmVtdWMuYWRkQ29ubmVjdGlvbkluZm9Ub1ByZXNlbmNlKHZhbHVlKTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIGNhc2UgXCJlbWFpbFwiOlxuICAgICAgICAgICAgICAgIGNvbm5lY3Rpb24uZW11Yy5hZGRFbWFpbFRvUHJlc2VuY2UodmFsdWUpO1xuICAgICAgICAgICAgZGVmYXVsdCA6XG4gICAgICAgICAgICAgICAgY29uc29sZS5sb2coXCJVbmtub3duIHRhZyBmb3IgcHJlc2VuY2UuXCIpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBpZighZG9udFNlbmQpXG4gICAgICAgICAgICBjb25uZWN0aW9uLmVtdWMuc2VuZFByZXNlbmNlKCk7XG4gICAgfSxcbiAgICBzZW5kTG9nczogZnVuY3Rpb24gKGNvbnRlbnQpIHtcbiAgICAgICAgLy8gWEVQLTAzMzctaXNoXG4gICAgICAgIHZhciBtZXNzYWdlID0gJG1zZyh7dG86IGZvY3VzTXVjSmlkLCB0eXBlOiAnbm9ybWFsJ30pO1xuICAgICAgICBtZXNzYWdlLmMoJ2xvZycsIHsgeG1sbnM6ICd1cm46eG1wcDpldmVudGxvZycsXG4gICAgICAgICAgICBpZDogJ1BlZXJDb25uZWN0aW9uU3RhdHMnfSk7XG4gICAgICAgIG1lc3NhZ2UuYygnbWVzc2FnZScpLnQoY29udGVudCkudXAoKTtcbiAgICAgICAgaWYgKGRlZmxhdGUpIHtcbiAgICAgICAgICAgIG1lc3NhZ2UuYygndGFnJywge25hbWU6IFwiZGVmbGF0ZWRcIiwgdmFsdWU6IFwidHJ1ZVwifSkudXAoKTtcbiAgICAgICAgfVxuICAgICAgICBtZXNzYWdlLnVwKCk7XG5cbiAgICAgICAgY29ubmVjdGlvbi5zZW5kKG1lc3NhZ2UpO1xuICAgIH0sXG4gICAgcG9wdWxhdGVEYXRhOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBkYXRhID0ge307XG4gICAgICAgIGlmIChjb25uZWN0aW9uLmppbmdsZSkge1xuICAgICAgICAgICAgZGF0YSA9IGNvbm5lY3Rpb24uamluZ2xlLnBvcHVsYXRlRGF0YSgpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBkYXRhO1xuICAgIH0sXG4gICAgZ2V0TG9nZ2VyOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGlmKGNvbm5lY3Rpb24ubG9nZ2VyKVxuICAgICAgICAgICAgcmV0dXJuIGNvbm5lY3Rpb24ubG9nZ2VyLmxvZztcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgfSxcbiAgICBnZXRQcmV6aTogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gY29ubmVjdGlvbi5lbXVjLmdldFByZXppKHRoaXMubXlKaWQoKSk7XG4gICAgfSxcbiAgICByZW1vdmVQcmV6aUZyb21QcmVzZW5jZTogZnVuY3Rpb24gKCkge1xuICAgICAgICBjb25uZWN0aW9uLmVtdWMucmVtb3ZlUHJlemlGcm9tUHJlc2VuY2UoKTtcbiAgICAgICAgY29ubmVjdGlvbi5lbXVjLnNlbmRQcmVzZW5jZSgpO1xuICAgIH0sXG4gICAgc2VuZENoYXRNZXNzYWdlOiBmdW5jdGlvbiAobWVzc2FnZSwgbmlja25hbWUpIHtcbiAgICAgICAgY29ubmVjdGlvbi5lbXVjLnNlbmRNZXNzYWdlKG1lc3NhZ2UsIG5pY2tuYW1lKTtcbiAgICB9LFxuICAgIHNldFN1YmplY3Q6IGZ1bmN0aW9uICh0b3BpYykge1xuICAgICAgICBjb25uZWN0aW9uLmVtdWMuc2V0U3ViamVjdCh0b3BpYyk7XG4gICAgfSxcbiAgICBsb2NrUm9vbTogZnVuY3Rpb24gKGtleSwgb25TdWNjZXNzLCBvbkVycm9yLCBvbk5vdFN1cHBvcnRlZCkge1xuICAgICAgICBjb25uZWN0aW9uLmVtdWMubG9ja1Jvb20oa2V5LCBvblN1Y2Nlc3MsIG9uRXJyb3IsIG9uTm90U3VwcG9ydGVkKTtcbiAgICB9LFxuICAgIGRpYWw6IGZ1bmN0aW9uICh0bywgZnJvbSwgcm9vbU5hbWUscm9vbVBhc3MpIHtcbiAgICAgICAgY29ubmVjdGlvbi5yYXlvLmRpYWwodG8sIGZyb20sIHJvb21OYW1lLHJvb21QYXNzKTtcbiAgICB9LFxuICAgIHNldE11dGU6IGZ1bmN0aW9uIChqaWQsIG11dGUpIHtcbiAgICAgICAgY29ubmVjdGlvbi5tb2RlcmF0ZS5zZXRNdXRlKGppZCwgbXV0ZSk7XG4gICAgfSxcbiAgICBlamVjdDogZnVuY3Rpb24gKGppZCkge1xuICAgICAgICBjb25uZWN0aW9uLm1vZGVyYXRlLmVqZWN0KGppZCk7XG4gICAgfSxcbiAgICBmaW5kSmlkRnJvbVJlc291cmNlOiBmdW5jdGlvbiAocmVzb3VyY2UpIHtcbiAgICAgICAgY29ubmVjdGlvbi5lbXVjLmZpbmRKaWRGcm9tUmVzb3VyY2UocmVzb3VyY2UpO1xuICAgIH0sXG4gICAgZ2V0TWVtYmVyczogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gY29ubmVjdGlvbi5lbXVjLm1lbWJlcnM7XG4gICAgfVxuXG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IFhNUFA7IiwiLy8gQ29weXJpZ2h0IEpveWVudCwgSW5jLiBhbmQgb3RoZXIgTm9kZSBjb250cmlidXRvcnMuXG4vL1xuLy8gUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGFcbi8vIGNvcHkgb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGVcbi8vIFwiU29mdHdhcmVcIiksIHRvIGRlYWwgaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZ1xuLy8gd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMgdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLFxuLy8gZGlzdHJpYnV0ZSwgc3VibGljZW5zZSwgYW5kL29yIHNlbGwgY29waWVzIG9mIHRoZSBTb2Z0d2FyZSwgYW5kIHRvIHBlcm1pdFxuLy8gcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcyBmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlXG4vLyBmb2xsb3dpbmcgY29uZGl0aW9uczpcbi8vXG4vLyBUaGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBzaGFsbCBiZSBpbmNsdWRlZFxuLy8gaW4gYWxsIGNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuXG4vL1xuLy8gVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEIFwiQVMgSVNcIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTU1xuLy8gT1IgSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRlxuLy8gTUVSQ0hBTlRBQklMSVRZLCBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBTkQgTk9OSU5GUklOR0VNRU5ULiBJTlxuLy8gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sXG4vLyBEQU1BR0VTIE9SIE9USEVSIExJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1Jcbi8vIE9USEVSV0lTRSwgQVJJU0lORyBGUk9NLCBPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBTT0ZUV0FSRSBPUiBUSEVcbi8vIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUgU09GVFdBUkUuXG5cbmZ1bmN0aW9uIEV2ZW50RW1pdHRlcigpIHtcbiAgdGhpcy5fZXZlbnRzID0gdGhpcy5fZXZlbnRzIHx8IHt9O1xuICB0aGlzLl9tYXhMaXN0ZW5lcnMgPSB0aGlzLl9tYXhMaXN0ZW5lcnMgfHwgdW5kZWZpbmVkO1xufVxubW9kdWxlLmV4cG9ydHMgPSBFdmVudEVtaXR0ZXI7XG5cbi8vIEJhY2t3YXJkcy1jb21wYXQgd2l0aCBub2RlIDAuMTAueFxuRXZlbnRFbWl0dGVyLkV2ZW50RW1pdHRlciA9IEV2ZW50RW1pdHRlcjtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5fZXZlbnRzID0gdW5kZWZpbmVkO1xuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5fbWF4TGlzdGVuZXJzID0gdW5kZWZpbmVkO1xuXG4vLyBCeSBkZWZhdWx0IEV2ZW50RW1pdHRlcnMgd2lsbCBwcmludCBhIHdhcm5pbmcgaWYgbW9yZSB0aGFuIDEwIGxpc3RlbmVycyBhcmVcbi8vIGFkZGVkIHRvIGl0LiBUaGlzIGlzIGEgdXNlZnVsIGRlZmF1bHQgd2hpY2ggaGVscHMgZmluZGluZyBtZW1vcnkgbGVha3MuXG5FdmVudEVtaXR0ZXIuZGVmYXVsdE1heExpc3RlbmVycyA9IDEwO1xuXG4vLyBPYnZpb3VzbHkgbm90IGFsbCBFbWl0dGVycyBzaG91bGQgYmUgbGltaXRlZCB0byAxMC4gVGhpcyBmdW5jdGlvbiBhbGxvd3Ncbi8vIHRoYXQgdG8gYmUgaW5jcmVhc2VkLiBTZXQgdG8gemVybyBmb3IgdW5saW1pdGVkLlxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5zZXRNYXhMaXN0ZW5lcnMgPSBmdW5jdGlvbihuKSB7XG4gIGlmICghaXNOdW1iZXIobikgfHwgbiA8IDAgfHwgaXNOYU4obikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCduIG11c3QgYmUgYSBwb3NpdGl2ZSBudW1iZXInKTtcbiAgdGhpcy5fbWF4TGlzdGVuZXJzID0gbjtcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG5FdmVudEVtaXR0ZXIucHJvdG90eXBlLmVtaXQgPSBmdW5jdGlvbih0eXBlKSB7XG4gIHZhciBlciwgaGFuZGxlciwgbGVuLCBhcmdzLCBpLCBsaXN0ZW5lcnM7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHMpXG4gICAgdGhpcy5fZXZlbnRzID0ge307XG5cbiAgLy8gSWYgdGhlcmUgaXMgbm8gJ2Vycm9yJyBldmVudCBsaXN0ZW5lciB0aGVuIHRocm93LlxuICBpZiAodHlwZSA9PT0gJ2Vycm9yJykge1xuICAgIGlmICghdGhpcy5fZXZlbnRzLmVycm9yIHx8XG4gICAgICAgIChpc09iamVjdCh0aGlzLl9ldmVudHMuZXJyb3IpICYmICF0aGlzLl9ldmVudHMuZXJyb3IubGVuZ3RoKSkge1xuICAgICAgZXIgPSBhcmd1bWVudHNbMV07XG4gICAgICBpZiAoZXIgaW5zdGFuY2VvZiBFcnJvcikge1xuICAgICAgICB0aHJvdyBlcjsgLy8gVW5oYW5kbGVkICdlcnJvcicgZXZlbnRcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRocm93IFR5cGVFcnJvcignVW5jYXVnaHQsIHVuc3BlY2lmaWVkIFwiZXJyb3JcIiBldmVudC4nKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gIH1cblxuICBoYW5kbGVyID0gdGhpcy5fZXZlbnRzW3R5cGVdO1xuXG4gIGlmIChpc1VuZGVmaW5lZChoYW5kbGVyKSlcbiAgICByZXR1cm4gZmFsc2U7XG5cbiAgaWYgKGlzRnVuY3Rpb24oaGFuZGxlcikpIHtcbiAgICBzd2l0Y2ggKGFyZ3VtZW50cy5sZW5ndGgpIHtcbiAgICAgIC8vIGZhc3QgY2FzZXNcbiAgICAgIGNhc2UgMTpcbiAgICAgICAgaGFuZGxlci5jYWxsKHRoaXMpO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgMjpcbiAgICAgICAgaGFuZGxlci5jYWxsKHRoaXMsIGFyZ3VtZW50c1sxXSk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAzOlxuICAgICAgICBoYW5kbGVyLmNhbGwodGhpcywgYXJndW1lbnRzWzFdLCBhcmd1bWVudHNbMl0pO1xuICAgICAgICBicmVhaztcbiAgICAgIC8vIHNsb3dlclxuICAgICAgZGVmYXVsdDpcbiAgICAgICAgbGVuID0gYXJndW1lbnRzLmxlbmd0aDtcbiAgICAgICAgYXJncyA9IG5ldyBBcnJheShsZW4gLSAxKTtcbiAgICAgICAgZm9yIChpID0gMTsgaSA8IGxlbjsgaSsrKVxuICAgICAgICAgIGFyZ3NbaSAtIDFdID0gYXJndW1lbnRzW2ldO1xuICAgICAgICBoYW5kbGVyLmFwcGx5KHRoaXMsIGFyZ3MpO1xuICAgIH1cbiAgfSBlbHNlIGlmIChpc09iamVjdChoYW5kbGVyKSkge1xuICAgIGxlbiA9IGFyZ3VtZW50cy5sZW5ndGg7XG4gICAgYXJncyA9IG5ldyBBcnJheShsZW4gLSAxKTtcbiAgICBmb3IgKGkgPSAxOyBpIDwgbGVuOyBpKyspXG4gICAgICBhcmdzW2kgLSAxXSA9IGFyZ3VtZW50c1tpXTtcblxuICAgIGxpc3RlbmVycyA9IGhhbmRsZXIuc2xpY2UoKTtcbiAgICBsZW4gPSBsaXN0ZW5lcnMubGVuZ3RoO1xuICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKylcbiAgICAgIGxpc3RlbmVyc1tpXS5hcHBseSh0aGlzLCBhcmdzKTtcbiAgfVxuXG4gIHJldHVybiB0cnVlO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5hZGRMaXN0ZW5lciA9IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKSB7XG4gIHZhciBtO1xuXG4gIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCdsaXN0ZW5lciBtdXN0IGJlIGEgZnVuY3Rpb24nKTtcblxuICBpZiAoIXRoaXMuX2V2ZW50cylcbiAgICB0aGlzLl9ldmVudHMgPSB7fTtcblxuICAvLyBUbyBhdm9pZCByZWN1cnNpb24gaW4gdGhlIGNhc2UgdGhhdCB0eXBlID09PSBcIm5ld0xpc3RlbmVyXCIhIEJlZm9yZVxuICAvLyBhZGRpbmcgaXQgdG8gdGhlIGxpc3RlbmVycywgZmlyc3QgZW1pdCBcIm5ld0xpc3RlbmVyXCIuXG4gIGlmICh0aGlzLl9ldmVudHMubmV3TGlzdGVuZXIpXG4gICAgdGhpcy5lbWl0KCduZXdMaXN0ZW5lcicsIHR5cGUsXG4gICAgICAgICAgICAgIGlzRnVuY3Rpb24obGlzdGVuZXIubGlzdGVuZXIpID9cbiAgICAgICAgICAgICAgbGlzdGVuZXIubGlzdGVuZXIgOiBsaXN0ZW5lcik7XG5cbiAgaWYgKCF0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgLy8gT3B0aW1pemUgdGhlIGNhc2Ugb2Ygb25lIGxpc3RlbmVyLiBEb24ndCBuZWVkIHRoZSBleHRyYSBhcnJheSBvYmplY3QuXG4gICAgdGhpcy5fZXZlbnRzW3R5cGVdID0gbGlzdGVuZXI7XG4gIGVsc2UgaWYgKGlzT2JqZWN0KHRoaXMuX2V2ZW50c1t0eXBlXSkpXG4gICAgLy8gSWYgd2UndmUgYWxyZWFkeSBnb3QgYW4gYXJyYXksIGp1c3QgYXBwZW5kLlxuICAgIHRoaXMuX2V2ZW50c1t0eXBlXS5wdXNoKGxpc3RlbmVyKTtcbiAgZWxzZVxuICAgIC8vIEFkZGluZyB0aGUgc2Vjb25kIGVsZW1lbnQsIG5lZWQgdG8gY2hhbmdlIHRvIGFycmF5LlxuICAgIHRoaXMuX2V2ZW50c1t0eXBlXSA9IFt0aGlzLl9ldmVudHNbdHlwZV0sIGxpc3RlbmVyXTtcblxuICAvLyBDaGVjayBmb3IgbGlzdGVuZXIgbGVha1xuICBpZiAoaXNPYmplY3QodGhpcy5fZXZlbnRzW3R5cGVdKSAmJiAhdGhpcy5fZXZlbnRzW3R5cGVdLndhcm5lZCkge1xuICAgIHZhciBtO1xuICAgIGlmICghaXNVbmRlZmluZWQodGhpcy5fbWF4TGlzdGVuZXJzKSkge1xuICAgICAgbSA9IHRoaXMuX21heExpc3RlbmVycztcbiAgICB9IGVsc2Uge1xuICAgICAgbSA9IEV2ZW50RW1pdHRlci5kZWZhdWx0TWF4TGlzdGVuZXJzO1xuICAgIH1cblxuICAgIGlmIChtICYmIG0gPiAwICYmIHRoaXMuX2V2ZW50c1t0eXBlXS5sZW5ndGggPiBtKSB7XG4gICAgICB0aGlzLl9ldmVudHNbdHlwZV0ud2FybmVkID0gdHJ1ZTtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJyhub2RlKSB3YXJuaW5nOiBwb3NzaWJsZSBFdmVudEVtaXR0ZXIgbWVtb3J5ICcgK1xuICAgICAgICAgICAgICAgICAgICAnbGVhayBkZXRlY3RlZC4gJWQgbGlzdGVuZXJzIGFkZGVkLiAnICtcbiAgICAgICAgICAgICAgICAgICAgJ1VzZSBlbWl0dGVyLnNldE1heExpc3RlbmVycygpIHRvIGluY3JlYXNlIGxpbWl0LicsXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX2V2ZW50c1t0eXBlXS5sZW5ndGgpO1xuICAgICAgaWYgKHR5cGVvZiBjb25zb2xlLnRyYWNlID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIC8vIG5vdCBzdXBwb3J0ZWQgaW4gSUUgMTBcbiAgICAgICAgY29uc29sZS50cmFjZSgpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5vbiA9IEV2ZW50RW1pdHRlci5wcm90b3R5cGUuYWRkTGlzdGVuZXI7XG5cbkV2ZW50RW1pdHRlci5wcm90b3R5cGUub25jZSA9IGZ1bmN0aW9uKHR5cGUsIGxpc3RlbmVyKSB7XG4gIGlmICghaXNGdW5jdGlvbihsaXN0ZW5lcikpXG4gICAgdGhyb3cgVHlwZUVycm9yKCdsaXN0ZW5lciBtdXN0IGJlIGEgZnVuY3Rpb24nKTtcblxuICB2YXIgZmlyZWQgPSBmYWxzZTtcblxuICBmdW5jdGlvbiBnKCkge1xuICAgIHRoaXMucmVtb3ZlTGlzdGVuZXIodHlwZSwgZyk7XG5cbiAgICBpZiAoIWZpcmVkKSB7XG4gICAgICBmaXJlZCA9IHRydWU7XG4gICAgICBsaXN0ZW5lci5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgIH1cbiAgfVxuXG4gIGcubGlzdGVuZXIgPSBsaXN0ZW5lcjtcbiAgdGhpcy5vbih0eXBlLCBnKTtcblxuICByZXR1cm4gdGhpcztcbn07XG5cbi8vIGVtaXRzIGEgJ3JlbW92ZUxpc3RlbmVyJyBldmVudCBpZmYgdGhlIGxpc3RlbmVyIHdhcyByZW1vdmVkXG5FdmVudEVtaXR0ZXIucHJvdG90eXBlLnJlbW92ZUxpc3RlbmVyID0gZnVuY3Rpb24odHlwZSwgbGlzdGVuZXIpIHtcbiAgdmFyIGxpc3QsIHBvc2l0aW9uLCBsZW5ndGgsIGk7XG5cbiAgaWYgKCFpc0Z1bmN0aW9uKGxpc3RlbmVyKSlcbiAgICB0aHJvdyBUeXBlRXJyb3IoJ2xpc3RlbmVyIG11c3QgYmUgYSBmdW5jdGlvbicpO1xuXG4gIGlmICghdGhpcy5fZXZlbnRzIHx8ICF0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgcmV0dXJuIHRoaXM7XG5cbiAgbGlzdCA9IHRoaXMuX2V2ZW50c1t0eXBlXTtcbiAgbGVuZ3RoID0gbGlzdC5sZW5ndGg7XG4gIHBvc2l0aW9uID0gLTE7XG5cbiAgaWYgKGxpc3QgPT09IGxpc3RlbmVyIHx8XG4gICAgICAoaXNGdW5jdGlvbihsaXN0Lmxpc3RlbmVyKSAmJiBsaXN0Lmxpc3RlbmVyID09PSBsaXN0ZW5lcikpIHtcbiAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuICAgIGlmICh0aGlzLl9ldmVudHMucmVtb3ZlTGlzdGVuZXIpXG4gICAgICB0aGlzLmVtaXQoJ3JlbW92ZUxpc3RlbmVyJywgdHlwZSwgbGlzdGVuZXIpO1xuXG4gIH0gZWxzZSBpZiAoaXNPYmplY3QobGlzdCkpIHtcbiAgICBmb3IgKGkgPSBsZW5ndGg7IGktLSA+IDA7KSB7XG4gICAgICBpZiAobGlzdFtpXSA9PT0gbGlzdGVuZXIgfHxcbiAgICAgICAgICAobGlzdFtpXS5saXN0ZW5lciAmJiBsaXN0W2ldLmxpc3RlbmVyID09PSBsaXN0ZW5lcikpIHtcbiAgICAgICAgcG9zaXRpb24gPSBpO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAocG9zaXRpb24gPCAwKVxuICAgICAgcmV0dXJuIHRoaXM7XG5cbiAgICBpZiAobGlzdC5sZW5ndGggPT09IDEpIHtcbiAgICAgIGxpc3QubGVuZ3RoID0gMDtcbiAgICAgIGRlbGV0ZSB0aGlzLl9ldmVudHNbdHlwZV07XG4gICAgfSBlbHNlIHtcbiAgICAgIGxpc3Quc3BsaWNlKHBvc2l0aW9uLCAxKTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKVxuICAgICAgdGhpcy5lbWl0KCdyZW1vdmVMaXN0ZW5lcicsIHR5cGUsIGxpc3RlbmVyKTtcbiAgfVxuXG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5yZW1vdmVBbGxMaXN0ZW5lcnMgPSBmdW5jdGlvbih0eXBlKSB7XG4gIHZhciBrZXksIGxpc3RlbmVycztcblxuICBpZiAoIXRoaXMuX2V2ZW50cylcbiAgICByZXR1cm4gdGhpcztcblxuICAvLyBub3QgbGlzdGVuaW5nIGZvciByZW1vdmVMaXN0ZW5lciwgbm8gbmVlZCB0byBlbWl0XG4gIGlmICghdGhpcy5fZXZlbnRzLnJlbW92ZUxpc3RlbmVyKSB7XG4gICAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPT09IDApXG4gICAgICB0aGlzLl9ldmVudHMgPSB7fTtcbiAgICBlbHNlIGlmICh0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgLy8gZW1pdCByZW1vdmVMaXN0ZW5lciBmb3IgYWxsIGxpc3RlbmVycyBvbiBhbGwgZXZlbnRzXG4gIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAwKSB7XG4gICAgZm9yIChrZXkgaW4gdGhpcy5fZXZlbnRzKSB7XG4gICAgICBpZiAoa2V5ID09PSAncmVtb3ZlTGlzdGVuZXInKSBjb250aW51ZTtcbiAgICAgIHRoaXMucmVtb3ZlQWxsTGlzdGVuZXJzKGtleSk7XG4gICAgfVxuICAgIHRoaXMucmVtb3ZlQWxsTGlzdGVuZXJzKCdyZW1vdmVMaXN0ZW5lcicpO1xuICAgIHRoaXMuX2V2ZW50cyA9IHt9O1xuICAgIHJldHVybiB0aGlzO1xuICB9XG5cbiAgbGlzdGVuZXJzID0gdGhpcy5fZXZlbnRzW3R5cGVdO1xuXG4gIGlmIChpc0Z1bmN0aW9uKGxpc3RlbmVycykpIHtcbiAgICB0aGlzLnJlbW92ZUxpc3RlbmVyKHR5cGUsIGxpc3RlbmVycyk7XG4gIH0gZWxzZSB7XG4gICAgLy8gTElGTyBvcmRlclxuICAgIHdoaWxlIChsaXN0ZW5lcnMubGVuZ3RoKVxuICAgICAgdGhpcy5yZW1vdmVMaXN0ZW5lcih0eXBlLCBsaXN0ZW5lcnNbbGlzdGVuZXJzLmxlbmd0aCAtIDFdKTtcbiAgfVxuICBkZWxldGUgdGhpcy5fZXZlbnRzW3R5cGVdO1xuXG4gIHJldHVybiB0aGlzO1xufTtcblxuRXZlbnRFbWl0dGVyLnByb3RvdHlwZS5saXN0ZW5lcnMgPSBmdW5jdGlvbih0eXBlKSB7XG4gIHZhciByZXQ7XG4gIGlmICghdGhpcy5fZXZlbnRzIHx8ICF0aGlzLl9ldmVudHNbdHlwZV0pXG4gICAgcmV0ID0gW107XG4gIGVsc2UgaWYgKGlzRnVuY3Rpb24odGhpcy5fZXZlbnRzW3R5cGVdKSlcbiAgICByZXQgPSBbdGhpcy5fZXZlbnRzW3R5cGVdXTtcbiAgZWxzZVxuICAgIHJldCA9IHRoaXMuX2V2ZW50c1t0eXBlXS5zbGljZSgpO1xuICByZXR1cm4gcmV0O1xufTtcblxuRXZlbnRFbWl0dGVyLmxpc3RlbmVyQ291bnQgPSBmdW5jdGlvbihlbWl0dGVyLCB0eXBlKSB7XG4gIHZhciByZXQ7XG4gIGlmICghZW1pdHRlci5fZXZlbnRzIHx8ICFlbWl0dGVyLl9ldmVudHNbdHlwZV0pXG4gICAgcmV0ID0gMDtcbiAgZWxzZSBpZiAoaXNGdW5jdGlvbihlbWl0dGVyLl9ldmVudHNbdHlwZV0pKVxuICAgIHJldCA9IDE7XG4gIGVsc2VcbiAgICByZXQgPSBlbWl0dGVyLl9ldmVudHNbdHlwZV0ubGVuZ3RoO1xuICByZXR1cm4gcmV0O1xufTtcblxuZnVuY3Rpb24gaXNGdW5jdGlvbihhcmcpIHtcbiAgcmV0dXJuIHR5cGVvZiBhcmcgPT09ICdmdW5jdGlvbic7XG59XG5cbmZ1bmN0aW9uIGlzTnVtYmVyKGFyZykge1xuICByZXR1cm4gdHlwZW9mIGFyZyA9PT0gJ251bWJlcic7XG59XG5cbmZ1bmN0aW9uIGlzT2JqZWN0KGFyZykge1xuICByZXR1cm4gdHlwZW9mIGFyZyA9PT0gJ29iamVjdCcgJiYgYXJnICE9PSBudWxsO1xufVxuXG5mdW5jdGlvbiBpc1VuZGVmaW5lZChhcmcpIHtcbiAgcmV0dXJuIGFyZyA9PT0gdm9pZCAwO1xufVxuIl19 diff --git a/libs/rayo.js b/libs/rayo.js deleted file mode 100644 index 3298093f8..000000000 --- a/libs/rayo.js +++ /dev/null @@ -1,103 +0,0 @@ -/* jshint -W117 */ -Strophe.addConnectionPlugin('rayo', - { - RAYO_XMLNS: 'urn:xmpp:rayo:1', - connection: null, - init: function (conn) - { - this.connection = conn; - if (this.connection.disco) - { - this.connection.disco.addFeature('urn:xmpp:rayo:client:1'); - } - - this.connection.addHandler( - this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null); - }, - onRayo: function (iq) - { - console.info("Rayo IQ", iq); - }, - dial: function (to, from, roomName, roomPass) - { - var self = this; - var req = $iq( - { - type: 'set', - to: focusMucJid - } - ); - req.c('dial', - { - xmlns: this.RAYO_XMLNS, - to: to, - from: from - }); - req.c('header', - { - name: 'JvbRoomName', - value: roomName - }).up(); - - if (roomPass !== null && roomPass.length) { - - req.c('header', - { - name: 'JvbRoomPassword', - value: roomPass - }).up(); - } - - this.connection.sendIQ( - req, - function (result) - { - console.info('Dial result ', result); - - var resource = $(result).find('ref').attr('uri'); - this.call_resource = resource.substr('xmpp:'.length); - console.info( - "Received call resource: " + this.call_resource); - }, - function (error) - { - console.info('Dial error ', error); - } - ); - }, - hang_up: function () - { - if (!this.call_resource) - { - console.warn("No call in progress"); - return; - } - - var self = this; - var req = $iq( - { - type: 'set', - to: this.call_resource - } - ); - req.c('hangup', - { - xmlns: this.RAYO_XMLNS - }); - - this.connection.sendIQ( - req, - function (result) - { - console.info('Hangup result ', result); - self.call_resource = null; - }, - function (error) - { - console.info('Hangup error ', error); - self.call_resource = null; - } - ); - } - } -); \ No newline at end of file diff --git a/libs/strophe/strophe.jingle.js b/libs/strophe/strophe.jingle.js deleted file mode 100644 index cbc081798..000000000 --- a/libs/strophe/strophe.jingle.js +++ /dev/null @@ -1,327 +0,0 @@ -/* jshint -W117 */ - - -function CallIncomingJingle(sid) { - var sess = connection.jingle.sessions[sid]; - - // TODO: do we check activecall == null? - activecall = sess; - - statistics.onConferenceCreated(sess); - RTC.onConferenceCreated(sess); - - // TODO: check affiliation and/or role - console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]); - sess.usedrip = true; // not-so-naive trickle ice - sess.sendAnswer(); - sess.accept(); - -}; - -Strophe.addConnectionPlugin('jingle', { - connection: null, - sessions: {}, - jid2session: {}, - ice_config: {iceServers: []}, - pc_constraints: {}, - media_constraints: { - mandatory: { - 'OfferToReceiveAudio': true, - 'OfferToReceiveVideo': true - } - // MozDontOfferDataChannel: true when this is firefox - }, - init: function (conn) { - this.connection = conn; - if (this.connection.disco) { - // http://xmpp.org/extensions/xep-0167.html#support - // http://xmpp.org/extensions/xep-0176.html#support - this.connection.disco.addFeature('urn:xmpp:jingle:1'); - this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1'); - this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1'); - this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio'); - this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video'); - - - // this is dealt with by SDP O/A so we don't need to annouce this - //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293 - //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294 - if (config.useRtcpMux) { - this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux - } - if (config.useBundle) { - this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle - } - //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc - } - this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null); - }, - onJingle: function (iq) { - var sid = $(iq).find('jingle').attr('sid'); - var action = $(iq).find('jingle').attr('action'); - var fromJid = iq.getAttribute('from'); - // send ack first - var ack = $iq({type: 'result', - to: fromJid, - id: iq.getAttribute('id') - }); - console.log('on jingle ' + action + ' from ' + fromJid, iq); - var sess = this.sessions[sid]; - if ('session-initiate' != action) { - if (sess === null) { - ack.type = 'error'; - ack.c('error', {type: 'cancel'}) - .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up() - .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'}); - this.connection.send(ack); - return true; - } - // compare from to sess.peerjid (bare jid comparison for later compat with message-mode) - // local jid is not checked - if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) { - console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid); - ack.type = 'error'; - ack.c('error', {type: 'cancel'}) - .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up() - .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'}); - this.connection.send(ack); - return true; - } - } else if (sess !== undefined) { - // existing session with same session id - // this might be out-of-order if the sess.peerjid is the same as from - ack.type = 'error'; - ack.c('error', {type: 'cancel'}) - .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up(); - console.warn('duplicate session id', sid); - this.connection.send(ack); - return true; - } - // FIXME: check for a defined action - this.connection.send(ack); - // see http://xmpp.org/extensions/xep-0166.html#concepts-session - switch (action) { - case 'session-initiate': - sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection); - // configure session - - sess.media_constraints = this.media_constraints; - sess.pc_constraints = this.pc_constraints; - sess.ice_config = this.ice_config; - - sess.initiate(fromJid, false); - // FIXME: setRemoteDescription should only be done when this call is to be accepted - sess.setRemoteDescription($(iq).find('>jingle'), 'offer'); - - this.sessions[sess.sid] = sess; - this.jid2session[sess.peerjid] = sess; - - // the callback should either - // .sendAnswer and .accept - // or .sendTerminate -- not necessarily synchronus - CallIncomingJingle(sess.sid); - break; - case 'session-accept': - sess.setRemoteDescription($(iq).find('>jingle'), 'answer'); - sess.accept(); - $(document).trigger('callaccepted.jingle', [sess.sid]); - break; - case 'session-terminate': - // If this is not the focus sending the terminate, we have - // nothing more to do here. - if (Object.keys(this.sessions).length < 1 - || !(this.sessions[Object.keys(this.sessions)[0]] - instanceof JingleSession)) - { - break; - } - console.log('terminating...', sess.sid); - sess.terminate(); - this.terminate(sess.sid); - if ($(iq).find('>jingle>reason').length) { - $(document).trigger('callterminated.jingle', [ - sess.sid, - sess.peerjid, - $(iq).find('>jingle>reason>:first')[0].tagName, - $(iq).find('>jingle>reason>text').text() - ]); - } else { - $(document).trigger('callterminated.jingle', - [sess.sid, sess.peerjid]); - } - break; - case 'transport-info': - sess.addIceCandidate($(iq).find('>jingle>content')); - break; - case 'session-info': - var affected; - if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { - $(document).trigger('ringing.jingle', [sess.sid]); - } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { - affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name'); - $(document).trigger('mute.jingle', [sess.sid, affected]); - } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { - affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name'); - $(document).trigger('unmute.jingle', [sess.sid, affected]); - } - break; - case 'addsource': // FIXME: proprietary, un-jingleish - case 'source-add': // FIXME: proprietary - sess.addSource($(iq).find('>jingle>content'), fromJid); - break; - case 'removesource': // FIXME: proprietary, un-jingleish - case 'source-remove': // FIXME: proprietary - sess.removeSource($(iq).find('>jingle>content'), fromJid); - break; - default: - console.warn('jingle action not implemented', action); - break; - } - return true; - }, - initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid - var sess = new JingleSession(myjid || this.connection.jid, - Math.random().toString(36).substr(2, 12), // random string - this.connection); - // configure session - - sess.media_constraints = this.media_constraints; - sess.pc_constraints = this.pc_constraints; - sess.ice_config = this.ice_config; - - sess.initiate(peerjid, true); - this.sessions[sess.sid] = sess; - this.jid2session[sess.peerjid] = sess; - sess.sendOffer(); - return sess; - }, - terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions) - if (sid === null || sid === undefined) { - for (sid in this.sessions) { - if (this.sessions[sid].state != 'ended') { - this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text); - this.sessions[sid].terminate(); - } - delete this.jid2session[this.sessions[sid].peerjid]; - delete this.sessions[sid]; - } - } else if (this.sessions.hasOwnProperty(sid)) { - if (this.sessions[sid].state != 'ended') { - this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text); - this.sessions[sid].terminate(); - } - delete this.jid2session[this.sessions[sid].peerjid]; - delete this.sessions[sid]; - } - }, - // Used to terminate a session when an unavailable presence is received. - terminateByJid: function (jid) { - if (this.jid2session.hasOwnProperty(jid)) { - var sess = this.jid2session[jid]; - if (sess) { - sess.terminate(); - console.log('peer went away silently', jid); - delete this.sessions[sess.sid]; - delete this.jid2session[jid]; - $(document).trigger('callterminated.jingle', - [sess.sid, jid], 'gone'); - } - } - }, - terminateRemoteByJid: function (jid, reason) { - if (this.jid2session.hasOwnProperty(jid)) { - var sess = this.jid2session[jid]; - if (sess) { - sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null); - sess.terminate(); - console.log('terminate peer with jid', sess.sid, jid); - delete this.sessions[sess.sid]; - delete this.jid2session[jid]; - $(document).trigger('callterminated.jingle', - [sess.sid, jid, 'kicked']); - } - } - }, - getStunAndTurnCredentials: function () { - // get stun and turn configuration from server via xep-0215 - // uses time-limited credentials as described in - // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 - // - // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua - // for a prosody module which implements this - // - // currently, this doesn't work with updateIce and therefore credentials with a long - // validity have to be fetched before creating the peerconnection - // TODO: implement refresh via updateIce as described in - // https://code.google.com/p/webrtc/issues/detail?id=1650 - var self = this; - this.connection.sendIQ( - $iq({type: 'get', to: this.connection.domain}) - .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}), - function (res) { - var iceservers = []; - $(res).find('>services>service').each(function (idx, el) { - el = $(el); - var dict = {}; - var type = el.attr('type'); - switch (type) { - case 'stun': - dict.url = 'stun:' + el.attr('host'); - if (el.attr('port')) { - dict.url += ':' + el.attr('port'); - } - iceservers.push(dict); - break; - case 'turn': - case 'turns': - dict.url = type + ':'; - if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508 - if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) { - dict.url += el.attr('username') + '@'; - } else { - dict.username = el.attr('username'); // only works in M28 - } - } - dict.url += el.attr('host'); - if (el.attr('port') && el.attr('port') != '3478') { - dict.url += ':' + el.attr('port'); - } - if (el.attr('transport') && el.attr('transport') != 'udp') { - dict.url += '?transport=' + el.attr('transport'); - } - if (el.attr('password')) { - dict.credential = el.attr('password'); - } - iceservers.push(dict); - break; - } - }); - self.ice_config.iceServers = iceservers; - }, - function (err) { - console.warn('getting turn credentials failed', err); - console.warn('is mod_turncredentials or similar installed?'); - } - ); - // implement push? - }, - - /** - * Populates the log data - */ - populateData: function () { - var data = {}; - Object.keys(this.sessions).forEach(function (sid) { - var session = this.sessions[sid]; - if (session.peerconnection && session.peerconnection.updateLog) { - // FIXME: should probably be a .dump call - data["jingle_" + session.sid] = { - updateLog: session.peerconnection.updateLog, - stats: session.peerconnection.stats, - url: window.location.href - }; - } - }); - return data; - } -}); diff --git a/libs/strophe/strophe.util.js b/libs/strophe/strophe.util.js deleted file mode 100644 index 126ecf633..000000000 --- a/libs/strophe/strophe.util.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Strophe logger implementation. Logs from level WARN and above. - */ -Strophe.log = function (level, msg) { - switch(level) { - case Strophe.LogLevel.WARN: - console.warn("Strophe: "+msg); - break; - case Strophe.LogLevel.ERROR: - case Strophe.LogLevel.FATAL: - console.error("Strophe: "+msg); - break; - } -}; - -Strophe.getStatusString = function(status) -{ - switch (status) - { - case Strophe.Status.ERROR: - return "ERROR"; - case Strophe.Status.CONNECTING: - return "CONNECTING"; - case Strophe.Status.CONNFAIL: - return "CONNFAIL"; - case Strophe.Status.AUTHENTICATING: - return "AUTHENTICATING"; - case Strophe.Status.AUTHFAIL: - return "AUTHFAIL"; - case Strophe.Status.CONNECTED: - return "CONNECTED"; - case Strophe.Status.DISCONNECTED: - return "DISCONNECTED"; - case Strophe.Status.DISCONNECTING: - return "DISCONNECTING"; - case Strophe.Status.ATTACHED: - return "ATTACHED"; - default: - return "unknown"; - } -}; diff --git a/moderatemuc.js b/moderatemuc.js deleted file mode 100644 index e64821dd8..000000000 --- a/moderatemuc.js +++ /dev/null @@ -1,56 +0,0 @@ -/* global $, $iq, config, connection, focusMucJid, forceMuted, - setAudioMuted, Strophe, toggleAudio */ -/** - * Moderate connection plugin. - */ -Strophe.addConnectionPlugin('moderate', { - connection: null, - init: function (conn) { - this.connection = conn; - - this.connection.addHandler(this.onMute.bind(this), - 'http://jitsi.org/jitmeet/audio', - 'iq', - 'set', - null, - null); - }, - setMute: function (jid, mute) { - console.info("set mute", mute); - var iqToFocus = $iq({to: focusMucJid, type: 'set'}) - .c('mute', { - xmlns: 'http://jitsi.org/jitmeet/audio', - jid: jid - }) - .t(mute.toString()) - .up(); - - this.connection.sendIQ( - iqToFocus, - function (result) { - console.log('set mute', result); - }, - function (error) { - console.log('set mute error', error); - }); - }, - onMute: function (iq) { - var from = iq.getAttribute('from'); - if (from !== focusMucJid) { - console.warn("Ignored mute from non focus peer"); - return false; - } - var mute = $(iq).find('mute'); - if (mute.length) { - var doMuteAudio = mute.text() === "true"; - setAudioMuted(doMuteAudio); - forceMuted = doMuteAudio; - } - return true; - }, - eject: function (jid) { - // We're not the focus, so can't terminate - //connection.jingle.terminateRemoteByJid(jid, 'kick'); - connection.emuc.kick(jid); - } -}); \ No newline at end of file diff --git a/modules/API/API.js b/modules/API/API.js index 934841629..b00a77e50 100644 --- a/modules/API/API.js +++ b/modules/API/API.js @@ -18,8 +18,8 @@ var commands = { displayName: UI.inputDisplayNameHandler, - muteAudio: toggleAudio, - muteVideo: toggleVideo, + muteAudio: UI.toggleAudio, + muteVideo: UI.toggleVideo, toggleFilmStrip: UI.toggleFilmStrip, toggleChat: UI.toggleChat, toggleContactList: UI.toggleContactList diff --git a/modules/RTC/DataChannels.js b/modules/RTC/DataChannels.js index bf981af30..b9e74e994 100644 --- a/modules/RTC/DataChannels.js +++ b/modules/RTC/DataChannels.js @@ -1,4 +1,4 @@ -/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/ +/* global Strophe, updateLargeVideo, focusedVideoSrc*/ // cache datachannels to avoid garbage collection // https://code.google.com/p/chromium/issues/detail?id=405545 @@ -91,7 +91,7 @@ var DataChannels = newValue = new Boolean(newValue).valueOf(); } } - $(document).trigger('inlastnchanged', [oldValue, newValue]); + UI.onLastNChanged(oldValue, newValue); } else if ("LastNEndpointsChangeEvent" === colibriClass) { diff --git a/modules/RTC/RTC.js b/modules/RTC/RTC.js index c65ef0b69..9b269c8a4 100644 --- a/modules/RTC/RTC.js +++ b/modules/RTC/RTC.js @@ -58,7 +58,7 @@ var RTC = { createRemoteStream: function (data, sid, thessrc) { var remoteStream = new MediaStream(data, sid, thessrc, eventEmitter, this.getBrowserType()); - var jid = data.peerjid || connection.emuc.myroomjid; + var jid = data.peerjid || xmpp.myJid(); if(!this.remoteStreams[jid]) { this.remoteStreams[jid] = {}; } @@ -144,16 +144,7 @@ var RTC = { RTC.localVideo = this.createLocalStream(stream, type, true); // Stop the stream to trigger onended event for old stream oldStream.stop(); - if (activecall) { - // FIXME: will block switchInProgress on true value in case of exception - activecall.switchStreams(stream, oldStream, callback); - } else { - // We are done immediately - console.error("No conference handler"); - UI.messageHandler.showError('Error', - 'Unable to switch video stream.'); - callback(); - } + xmpp.switchStreams(stream, oldStream,callback); } }; diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 5ef0deb48..0fa850941 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -17,9 +17,11 @@ var PanelToggler = require("./side_pannels/SidePanelToggler"); var RoomNameGenerator = require("./welcome_page/RoomnameGenerator"); UI.messageHandler = require("./util/MessageHandler"); var messageHandler = UI.messageHandler; +var Authentication = require("./authentication/Authentication"); +var UIUtil = require("./util/UIUtil"); //var eventEmitter = new EventEmitter(); - +var roomName = null; function setupPrezi() @@ -39,7 +41,7 @@ function setupChat() } function setupToolbars() { - Toolbar.init(); + Toolbar.init(UI); Toolbar.setupButtonsFromConfig(); BottomToolbar.init(); } @@ -62,6 +64,16 @@ function streamHandler(stream) { } } +function onDisposeConference(unload) { + Toolbar.showAuthenticateButton(false); +}; + +function onDisplayNameChanged(jid, displayName) { + ContactList.onDisplayNameChange(jid, displayName); + SettingsMenu.onDisplayNameChange(jid, displayName); + VideoLayout.onDisplayNameChanged(jid, displayName); +} + function registerListeners() { RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); @@ -70,14 +82,7 @@ function registerListeners() { VideoLayout.onRemoteStreamAdded(stream); }, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED); - // Listen for large video size updates - document.getElementById('largeVideo') - .addEventListener('loadedmetadata', function (e) { - currentVideoWidth = this.videoWidth; - currentVideoHeight = this.videoHeight; - VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight); - }); - + VideoLayout.init(); statistics.addAudioLevelListener(function(jid, audioLevel) { @@ -104,8 +109,38 @@ function registerListeners() { desktopsharing.addListener( Toolbar.changeDesktopSharingButtonState, DesktopSharingEventTypes.SWITCHING_DONE); + xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference); + xmpp.addListener(XMPPEvents.KICKED, function () { + messageHandler.openMessageDialog("Session Terminated", + "Ouch! You have been kicked out of the meet!"); + }); + xmpp.addListener(XMPPEvents.BRIDGE_DOWN, function () { + messageHandler.showError("Error", + "Jitsi Videobridge is currently unavailable. Please try again later!"); + }); + xmpp.addListener(XMPPEvents.USER_ID_CHANGED, Avatar.setUserAvatar); + xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) { + for(stream in changedStreams) + { + // might need to update the direction if participant just went from sendrecv to recvonly + if (stream.type === 'video' || stream.type === 'screen') { + var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video'); + switch (stream.direction) { + case 'sendrecv': + el.show(); + break; + case 'recvonly': + el.hide(); + // FIXME: Check if we have to change large video + //VideoLayout.updateLargeVideo(el); + break; + } + } + } - + }); + xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged); + xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined); } function bindEvents() @@ -117,10 +152,6 @@ function bindEvents() function () { VideoLayout.resizeLargeVideoContainer(); VideoLayout.positionLarge(); - isFullScreen = document.fullScreen || - document.mozFullScreen || - document.webkitIsFullScreen; - } ); @@ -255,11 +286,6 @@ UI.start = function () { }; - -UI.setUserAvatar = function (jid, id) { - Avatar.setUserAvatar(jid, id); -}; - UI.toggleSmileys = function () { Chat.toggleSmileys(); }; @@ -278,7 +304,7 @@ UI.updateChatConversation = function (from, displayName, message) { return Chat.updateChatConversation(from, displayName, message); }; -UI.onMucJoined = function (jid, info) { +function onMucJoined(jid, info) { Toolbar.updateRoomUrl(window.location.href); document.getElementById('localNick').appendChild( document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)') @@ -293,15 +319,14 @@ UI.onMucJoined = function (jid, info) { // Show authenticate button if needed Toolbar.showAuthenticateButton( - Moderator.isExternalAuthEnabled() && !Moderator.isModerator()); + xmpp.isExternalAuthEnabled() && !xmpp.isModerator()); var displayName = !config.displayJids ? info.displayName : Strophe.getResourceFromJid(jid); if (displayName) - $(document).trigger('displaynamechanged', - ['localVideoContainer', displayName + ' (me)']); -}; + onDisplayNameChanged('localVideoContainer', displayName + ' (me)'); +} UI.initEtherpad = function (name) { Etherpad.init(name); @@ -357,23 +382,19 @@ UI.toggleContactList = function () { UI.onLocalRoleChange = function (jid, info, pres) { console.info("My role changed, new role: " + info.role); - var isModerator = Moderator.isModerator(); + var isModerator = xmpp.isModerator(); VideoLayout.showModeratorIndicator(); Toolbar.showAuthenticateButton( - Moderator.isExternalAuthEnabled() && !isModerator); + xmpp.isExternalAuthEnabled() && !isModerator); if (isModerator) { - Toolbar.closeAuthenticationWindow(); + Authentication.closeAuthenticationWindow(); messageHandler.notify( 'Me', 'connected', 'Moderator rights granted !'); } }; -UI.onDisposeConference = function (unload) { - Toolbar.showAuthenticateButton(false); -}; - UI.onModeratorStatusChanged = function (isModerator) { Toolbar.showSipCallButton(isModerator); @@ -414,40 +435,11 @@ UI.onPasswordReqiured = function (callback) { ); }; -UI.onAuthenticationRequired = function () { - // This is the loop that will wait for the room to be created by - // someone else. 'auth_required.moderator' will bring us back here. - authRetryId = window.setTimeout( - function () { - Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus); - }, 5000); - // Show prompt only if it's not open - if (authDialog !== null) { - return; - } - // extract room name from 'room@muc.server.net' - var room = roomName.substr(0, roomName.indexOf('@')); - - authDialog = messageHandler.openDialog( - 'Stop', - 'Authentication is required to create room:
' + room + - '
You can either authenticate to create the room or ' + - 'just wait for someone else to do so.', - true, - { - Authenticate: 'authNow' - }, - function (onSubmitEvent, submitValue) { - - // Do not close the dialog yet - onSubmitEvent.preventDefault(); - - // Open login popup - if (submitValue === 'authNow') { - Toolbar.authenticateClicked(); - } - } - ); +UI.onAuthenticationRequired = function (intervalCallback) { + Authentication.openAuthenticationDialog( + roomName, intervalCallback, function () { + Toolbar.authenticateClicked(); + }); }; UI.setRecordingButtonState = function (state) { @@ -511,6 +503,8 @@ UI.showLocalAudioIndicator = function (mute) { }; UI.generateRoomName = function() { + if(roomName) + return roomName; var roomnode = null; var path = window.location.pathname; @@ -540,6 +534,7 @@ UI.generateRoomName = function() { } roomName = roomnode + '@' + config.hosts.muc; + return roomName; }; @@ -556,25 +551,146 @@ UI.dockToolbar = function (isDock) { return ToolbarToggler.dockToolbar(isDock); }; +UI.getCreadentials = function () { + return { + bosh: document.getElementById('boshURL').value, + password: document.getElementById('password').value, + jid: document.getElementById('jid').value + }; +}; + +UI.disableConnect = function () { + document.getElementById('connect').disabled = true; +}; + +UI.showLoginPopup = function(callback) +{ + console.log('password is required'); + + UI.messageHandler.openTwoButtonDialog(null, + '

Password required

' + + '' + + '', + true, + "Ok", + function (e, v, m, f) { + if (v) { + var username = document.getElementById('passwordrequired.username'); + var password = document.getElementById('passwordrequired.password'); + + if (username.value !== null && password.value != null) { + callback(username.value, password.value); + } + } + }, + function (event) { + document.getElementById('passwordrequired.username').focus(); + } + ); +} + +UI.checkForNicknameAndJoin = function () { + + Authentication.closeAuthenticationDialog(); + Authentication.stopInterval(); + + var nick = null; + if (config.useNicks) { + nick = window.prompt('Your nickname (optional)'); + } + xmpp.joinRooom(roomName, config.useNicks, nick); +} + + function dump(elem, filename) { elem = elem.parentNode; elem.download = filename || 'meetlog.json'; elem.href = 'data:application/json;charset=utf-8,\n'; - var data = {}; - if (connection.jingle) { - data = connection.jingle.populateData(); - } + var data = xmpp.populateData(); var metadata = {}; metadata.time = new Date(); metadata.url = window.location.href; metadata.ua = navigator.userAgent; - if (connection.logger) { - metadata.xmpp = connection.logger.log; + var log = xmpp.getLogger(); + if (log) { + metadata.xmpp = log; } data.metadata = metadata; elem.href += encodeURIComponent(JSON.stringify(data, null, ' ')); return false; } +UI.getRoomName = function () { + return roomName; +} + +/** + * Mutes/unmutes the local video. + * + * @param mute true to mute the local video; otherwise, false + * @param options an object which specifies optional arguments such as the + * boolean key byUser with default value true which + * specifies whether the method was initiated in response to a user command (in + * contrast to an automatic decision taken by the application logic) + */ +function setVideoMute(mute, options) { + xmpp.setVideoMute( + mute, + function (mute) { + var video = $('#video'); + var communicativeClass = "icon-camera"; + var muteClass = "icon-camera icon-camera-disabled"; + + if (mute) { + video.removeClass(communicativeClass); + video.addClass(muteClass); + } else { + video.removeClass(muteClass); + video.addClass(communicativeClass); + } + }, + options); +} + +/** + * Mutes/unmutes the local video. + */ +UI.toggleVideo = function () { + UIUtil.buttonClick("#video", "icon-camera icon-camera-disabled"); + + setVideoMute(!RTC.localVideo.isMuted()); +}; + +/** + * Mutes / unmutes audio for the local participant. + */ +UI.toggleAudio = function() { + UI.setAudioMuted(!RTC.localAudio.isMuted()); +}; + +/** + * Sets muted audio state for the local participant. + */ +UI.setAudioMuted = function (mute) { + + if(!xmpp.setAudioMute(mute, function () { + UI.showLocalAudioIndicator(mute); + + UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled"); + })) + { + // We still click the button. + UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled"); + return; + } + +} + +UI.onLastNChanged = function (oldValue, newValue) { + if (config.muteLocalVideoIfNotInLastN) { + setVideoMute(!newValue, { 'byUser': false }); + } +} + module.exports = UI; diff --git a/modules/UI/audio_levels/AudioLevels.js b/modules/UI/audio_levels/AudioLevels.js index 350c353f0..115c54510 100644 --- a/modules/UI/audio_levels/AudioLevels.js +++ b/modules/UI/audio_levels/AudioLevels.js @@ -87,10 +87,10 @@ var AudioLevels = (function(my) { drawContext.drawImage(canvasCache, 0, 0); if(resourceJid === AudioLevels.LOCAL_LEVEL) { - if(!connection.emuc.myroomjid) { + if(!xmpp.myJid()) { return; } - resourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid); + resourceJid = xmpp.myResource(); } if(resourceJid === largeVideoResourceJid) { @@ -221,8 +221,8 @@ var AudioLevels = (function(my) { function getVideoSpanId(resourceJid) { var videoSpanId = null; if (resourceJid === AudioLevels.LOCAL_LEVEL - || (connection.emuc.myroomjid && resourceJid - === Strophe.getResourceFromJid(connection.emuc.myroomjid))) + || (xmpp.myResource() && resourceJid + === xmpp.myResource())) videoSpanId = 'localVideoContainer'; else videoSpanId = 'participant_' + resourceJid; diff --git a/modules/UI/authentication/Authentication.js b/modules/UI/authentication/Authentication.js new file mode 100644 index 000000000..1a568d5ce --- /dev/null +++ b/modules/UI/authentication/Authentication.js @@ -0,0 +1,84 @@ +/* Initial "authentication required" dialog */ +var authDialog = null; +/* Loop retry ID that wits for other user to create the room */ +var authRetryId = null; +var authenticationWindow = null; + +var Authentication = { + openAuthenticationDialog: function (roomName, intervalCallback, callback) { + // This is the loop that will wait for the room to be created by + // someone else. 'auth_required.moderator' will bring us back here. + authRetryId = window.setTimeout(intervalCallback , 5000); + // Show prompt only if it's not open + if (authDialog !== null) { + return; + } + // extract room name from 'room@muc.server.net' + var room = roomName.substr(0, roomName.indexOf('@')); + + authDialog = messageHandler.openDialog( + 'Stop', + 'Authentication is required to create room:
' + room + + '
You can either authenticate to create the room or ' + + 'just wait for someone else to do so.', + true, + { + Authenticate: 'authNow' + }, + function (onSubmitEvent, submitValue) { + + // Do not close the dialog yet + onSubmitEvent.preventDefault(); + + // Open login popup + if (submitValue === 'authNow') { + callback(); + } + } + ); + }, + closeAuthenticationWindow:function () { + if (authenticationWindow) { + authenticationWindow.close(); + authenticationWindow = null; + } + }, + focusAuthenticationWindow: function () { + // If auth window exists just bring it to the front + if (authenticationWindow) { + authenticationWindow.focus(); + return; + } + }, + closeAuthenticationDialog: function () { + // Close authentication dialog if opened + if (authDialog) { + UI.messageHandler.closeDialog(); + authDialog = null; + } + }, + createAuthenticationWindow: function (callback, url) { + authenticationWindow = messageHandler.openCenteredPopup( + url, 910, 660, + // On closed + function () { + // Close authentication dialog if opened + if (authDialog) { + messageHandler.closeDialog(); + authDialog = null; + } + callback(); + authenticationWindow = null; + }); + return authenticationWindow; + }, + stopInterval: function () { + // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice + if (authRetryId) { + window.clearTimeout(authRetryId); + authRetryId = null; + } + } +}; + +module.exports = Authentication; \ No newline at end of file diff --git a/modules/UI/avatar/Avatar.js b/modules/UI/avatar/Avatar.js index 60825ef7e..0e18477cd 100644 --- a/modules/UI/avatar/Avatar.js +++ b/modules/UI/avatar/Avatar.js @@ -12,7 +12,7 @@ function setVisibility(selector, show) { function isUserMuted(jid) { // XXX(gp) we may want to rename this method to something like // isUserStreaming, for example. - if (jid && jid != connection.emuc.myroomjid) { + if (jid && jid != xmpp.myJid()) { var resource = Strophe.getResourceFromJid(jid); if (!require("../videolayout/VideoLayout").isInLastN(resource)) { return true; @@ -26,7 +26,7 @@ function isUserMuted(jid) { } function getGravatarUrl(id, size) { - if(id === connection.emuc.myroomjid || !id) { + if(id === xmpp.myJid() || !id) { id = Settings.getSettings().uid; } return 'https://www.gravatar.com/avatar/' + @@ -57,7 +57,7 @@ var Avatar = { // set the avatar in the settings menu if it is local user and get the // local video container - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { $('#avatar').get(0).src = thumbUrl; thumbnail = $('#localVideoContainer'); } @@ -100,7 +100,7 @@ var Avatar = { var video = $('#participant_' + resourceJid + '>video'); var avatar = $('#avatar_' + resourceJid); - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { video = $('#localVideoWrapper>video'); } if (show === undefined || show === null) { @@ -130,7 +130,7 @@ var Avatar = { */ updateActiveSpeakerAvatarSrc: function (jid) { if (!jid) { - jid = connection.emuc.findJidFromResource( + jid = xmpp.findJidFromResource( require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid); } var avatar = $("#activeSpeakerAvatar")[0]; diff --git a/modules/UI/etherpad/Etherpad.js b/modules/UI/etherpad/Etherpad.js index 8fc4a25f2..6dcfab458 100644 --- a/modules/UI/etherpad/Etherpad.js +++ b/modules/UI/etherpad/Etherpad.js @@ -1,4 +1,4 @@ -/* global $, config, connection, dockToolbar, Moderator, +/* global $, config, dockToolbar, setLargeVideoVisible, Util */ var VideoLayout = require("../videolayout/VideoLayout"); @@ -30,8 +30,7 @@ function resize() { * Shares the Etherpad name with other participants. */ function shareEtherpad() { - connection.emuc.addEtherpadToPresence(etherpadName); - connection.emuc.sendPresence(); + xmpp.addToPresence("etherpad", etherpadName); } /** diff --git a/modules/UI/prezi/Prezi.js b/modules/UI/prezi/Prezi.js index 03885c1c4..7ceb70c2e 100644 --- a/modules/UI/prezi/Prezi.js +++ b/modules/UI/prezi/Prezi.js @@ -30,7 +30,7 @@ var Prezi = { * to load. */ openPreziDialog: function() { - var myprezi = connection.emuc.getPrezi(connection.emuc.myroomjid); + var myprezi = xmpp.getPrezi(); if (myprezi) { messageHandler.openTwoButtonDialog("Remove Prezi", "Are you sure you would like to remove your Prezi?", @@ -38,8 +38,7 @@ var Prezi = { "Remove", function(e,v,m,f) { if(v) { - connection.emuc.removePreziFromPresence(); - connection.emuc.sendPresence(); + xmpp.removePreziFromPresence(); } } ); @@ -91,9 +90,7 @@ var Prezi = { return false; } else { - connection.emuc - .addPreziToPresence(urlValue, 0); - connection.emuc.sendPresence(); + xmpp.addToPresence("prezi", urlValue); $.prompt.close(); } } @@ -151,7 +148,7 @@ function presentationAdded(event, jid, presUrl, currentSlide) { VideoLayout.resizeThumbnails(); var controlsEnabled = false; - if (jid === connection.emuc.myroomjid) + if (jid === xmpp.myJid()) controlsEnabled = true; setPresentationVisible(true); @@ -191,15 +188,14 @@ function presentationAdded(event, jid, presUrl, currentSlide) { preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) { console.log("prezi status", event.value); if (event.value == PreziPlayer.STATUS_CONTENT_READY) { - if (jid != connection.emuc.myroomjid) + if (jid != xmpp.myJid()) preziPlayer.flyToStep(currentSlide); } }); preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) { console.log("event value", event.value); - connection.emuc.addCurrentSlideToPresence(event.value); - connection.emuc.sendPresence(); + xmpp.addToPresence("preziSlide", event.value); }); $("#" + elementId).css( 'background-image', diff --git a/modules/UI/side_pannels/SidePanelToggler.js b/modules/UI/side_pannels/SidePanelToggler.js index 44bbf042d..938e869d4 100644 --- a/modules/UI/side_pannels/SidePanelToggler.js +++ b/modules/UI/side_pannels/SidePanelToggler.js @@ -4,6 +4,7 @@ var Settings = require("./settings/Settings"); var SettingsMenu = require("./settings/SettingsMenu"); var VideoLayout = require("../videolayout/VideoLayout"); var ToolbarToggler = require("../toolbars/ToolbarToggler"); +var UIUtil = require("../util/UIUtil"); /** * Toggler for the chat, contact list, settings menu, etc.. @@ -110,7 +111,7 @@ var PanelToggler = (function(my) { * @param onClose function to be called if the window is going to be closed */ var toggle = function(object, selector, onOpenComplete, onOpen, onClose) { - buttonClick(buttons[selector], "active"); + UIUtil.buttonClick(buttons[selector], "active"); if (object.isVisible()) { $("#toast-container").animate({ @@ -140,7 +141,7 @@ var PanelToggler = (function(my) { if(currentlyOpen) { var current = $(currentlyOpen); - buttonClick(buttons[currentlyOpen], "active"); + UIUtil.buttonClick(buttons[currentlyOpen], "active"); current.css('z-index', 4); setTimeout(function () { current.css('display', 'none'); diff --git a/modules/UI/side_pannels/chat/Chat.js b/modules/UI/side_pannels/chat/Chat.js index 09683fea0..6ce82bf30 100644 --- a/modules/UI/side_pannels/chat/Chat.js +++ b/modules/UI/side_pannels/chat/Chat.js @@ -1,4 +1,4 @@ -/* global $, Util, connection, nickname:true, showToolbar */ +/* global $, Util, nickname:true, showToolbar */ var Replacement = require("./Replacement"); var CommandsProcessor = require("./Commands"); var ToolbarToggler = require("../../toolbars/ToolbarToggler"); @@ -184,8 +184,7 @@ var Chat = (function (my) { nickname = val; window.localStorage.displayname = nickname; - connection.emuc.addDisplayNameToPresence(nickname); - connection.emuc.sendPresence(); + xmpp.addToPresence("displayName", nickname); Chat.setChatConversationMode(true); @@ -208,7 +207,7 @@ var Chat = (function (my) { else { var message = Util.escapeHtml(value); - connection.emuc.sendMessage(message, nickname); + xmpp.sendChatMessage(message, nickname); } } }); @@ -234,7 +233,7 @@ var Chat = (function (my) { my.updateChatConversation = function (from, displayName, message) { var divClassName = ''; - if (connection.emuc.myroomjid === from) { + if (xmpp.myJid() === from) { divClassName = "localuser"; } else { diff --git a/modules/UI/side_pannels/chat/Commands.js b/modules/UI/side_pannels/chat/Commands.js index a9d926f83..6883268c3 100644 --- a/modules/UI/side_pannels/chat/Commands.js +++ b/modules/UI/side_pannels/chat/Commands.js @@ -32,7 +32,7 @@ function getCommand(message) function processTopic(commandArguments) { var topic = Util.escapeHtml(commandArguments); - connection.emuc.setSubject(topic); + xmpp.setSubject(topic); } /** diff --git a/modules/UI/side_pannels/contactlist/ContactList.js b/modules/UI/side_pannels/contactlist/ContactList.js index 8a1b8ec8c..b4f3feb7f 100644 --- a/modules/UI/side_pannels/contactlist/ContactList.js +++ b/modules/UI/side_pannels/contactlist/ContactList.js @@ -46,23 +46,6 @@ function createDisplayNameParagraph(displayName) { } -/** - * Indicates that the display name has changed. - */ -$(document).bind( 'displaynamechanged', - function (event, peerJid, displayName) { - if (peerJid === 'localVideoContainer') - peerJid = connection.emuc.myroomjid; - - var resourceJid = Strophe.getResourceFromJid(peerJid); - - var contactName = $('#contactlist #' + resourceJid + '>p'); - - if (contactName && displayName && displayName.length > 0) - contactName.html(displayName); - }); - - function stopGlowing(glower) { window.clearInterval(notificationInterval); notificationInterval = false; @@ -127,7 +110,7 @@ var ContactList = { var clElement = contactlist.get(0); - if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid) + if (resourceJid === xmpp.myResource() && $('#contactlist>ul .title')[0].nextSibling.nextSibling) { clElement.insertBefore(newContact, $('#contactlist>ul .title')[0].nextSibling.nextSibling); @@ -182,6 +165,18 @@ var ContactList = { } else { contact.removeClass('clickable'); } + }, + + onDisplayNameChange: function (peerJid, displayName) { + if (peerJid === 'localVideoContainer') + peerJid = xmpp.myJid(); + + var resourceJid = Strophe.getResourceFromJid(peerJid); + + var contactName = $('#contactlist #' + resourceJid + '>p'); + + if (contactName && displayName && displayName.length > 0) + contactName.html(displayName); } }; diff --git a/modules/UI/side_pannels/settings/SettingsMenu.js b/modules/UI/side_pannels/settings/SettingsMenu.js index 82ee205f5..25fca6145 100644 --- a/modules/UI/side_pannels/settings/SettingsMenu.js +++ b/modules/UI/side_pannels/settings/SettingsMenu.js @@ -10,16 +10,15 @@ var SettingsMenu = { if(newDisplayName) { var displayName = Settings.setDisplayName(newDisplayName); - connection.emuc.addDisplayNameToPresence(displayName); + xmpp.addToPresence("displayName", displayName, true); } - connection.emuc.addEmailToPresence(newEmail); + xmpp.addToPresence("email", newEmail); var email = Settings.setEmail(newEmail); - connection.emuc.sendPresence(); - Avatar.setUserAvatar(connection.emuc.myroomjid, email); + Avatar.setUserAvatar(xmpp.myJid(), email); }, isVisible: function() { @@ -29,14 +28,15 @@ var SettingsMenu = { setDisplayName: function(newDisplayName) { var displayName = Settings.setDisplayName(newDisplayName); $('#setDisplayName').get(0).value = displayName; + }, + + onDisplayNameChange: function(peerJid, newDisplayName) { + if(peerJid === 'localVideoContainer' || + peerJid === xmpp.myJid()) { + this.setDisplayName(newDisplayName); + } } }; -$(document).bind('displaynamechanged', function(event, peerJid, newDisplayName) { - if(peerJid === 'localVideoContainer' || - peerJid === connection.emuc.myroomjid) { - SettingsMenu.setDisplayName(newDisplayName); - } -}); module.exports = SettingsMenu; \ No newline at end of file diff --git a/modules/UI/toolbars/Toolbar.js b/modules/UI/toolbars/Toolbar.js index 87597ed1b..55ba8949f 100644 --- a/modules/UI/toolbars/Toolbar.js +++ b/modules/UI/toolbars/Toolbar.js @@ -1,22 +1,24 @@ -/* global $, buttonClick, config, lockRoom, Moderator, roomName, - setSharedKey, sharedKey, Util */ +/* global $, buttonClick, config, lockRoom, + setSharedKey, Util */ var messageHandler = require("../util/MessageHandler"); var BottomToolbar = require("./BottomToolbar"); var Prezi = require("../prezi/Prezi"); var Etherpad = require("../etherpad/Etherpad"); var PanelToggler = require("../side_pannels/SidePanelToggler"); +var Authentication = require("../authentication/Authentication"); +var UIUtil = require("../util/UIUtil"); var roomUrl = null; var sharedKey = ''; -var authenticationWindow = null; +var UI = null; var buttonHandlers = { "toolbar_button_mute": function () { - return toggleAudio(); + return UI.toggleAudio(); }, "toolbar_button_camera": function () { - return toggleVideo(); + return UI.toggleVideo(); }, "toolbar_button_authentication": function () { return Toolbar.authenticateClicked(); @@ -44,7 +46,7 @@ var buttonHandlers = }, "toolbar_button_fullScreen": function() { - buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen"); + UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen"); return Toolbar.toggleFullScreen(); }, "toolbar_button_sip": function () { @@ -59,9 +61,7 @@ var buttonHandlers = }; function hangup() { - disposeConference(); - sessionTerminated = true; - connection.emuc.doLeave(); + xmpp.disposeConference(); if(config.enableWelcomePage) { setTimeout(function() @@ -90,7 +90,29 @@ function hangup() { */ function toggleRecording() { - Recording.toggleRecording(); + xmpp.toggleRecording(function (callback) { + UI.messageHandler.openTwoButtonDialog(null, + '

Enter recording token

' + + '', + false, + "Save", + function (e, v, m, f) { + if (v) { + var token = document.getElementById('recordingToken'); + + if (token.value) { + callback(Util.escapeHtml(token.value)); + } + } + }, + function (event) { + document.getElementById('recordingToken').focus(); + }, + function () { + } + ); + }, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState); } /** @@ -101,7 +123,7 @@ function lockRoom(lock) { if (lock) currentSharedKey = sharedKey; - connection.emuc.lockRoom(currentSharedKey, function (res) { + xmpp.lockRoom(currentSharedKey, function (res) { // password is required if (sharedKey) { @@ -183,9 +205,8 @@ function callSipButtonClicked() if (v) { var numberInput = document.getElementById('sipNumber'); if (numberInput.value) { - connection.rayo.dial( - numberInput.value, 'fromnumber', - roomName, sharedKey); + xmpp.dial(numberInput.value, 'fromnumber', + UI.getRoomName(), sharedKey); } } }, @@ -197,9 +218,10 @@ function callSipButtonClicked() var Toolbar = (function (my) { - my.init = function () { + my.init = function (ui) { for(var k in buttonHandlers) $("#" + k).click(buttonHandlers[k]); + UI = ui; } /** @@ -210,35 +232,15 @@ var Toolbar = (function (my) { sharedKey = sKey; }; - my.closeAuthenticationWindow = function () { - if (authenticationWindow) { - authenticationWindow.close(); - authenticationWindow = null; - } - } - my.authenticateClicked = function () { - // If auth window exists just bring it to the front - if (authenticationWindow) { - authenticationWindow.focus(); - return; - } + Authentication.focusAuthenticationWindow(); // Get authentication URL - Moderator.getAuthUrl(function (url) { + xmpp.getAuthUrl(UI.getRoomName(), function (url) { // Open popup with authentication URL - authenticationWindow = messageHandler.openCenteredPopup( - url, 910, 660, - // On closed - function () { - // Close authentication dialog if opened - if (authDialog) { - messageHandler.closeDialog(); - authDialog = null; - } - // On popup closed - retry room allocation - Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus); - authenticationWindow = null; - }); + var authenticationWindow = Authentication.createAuthenticationWindow(function () { + // On popup closed - retry room allocation + xmpp.allocateConferenceFocus(UI.getRoomName(), UI.checkForNicknameAndJoin); + }, url); if (!authenticationWindow) { Toolbar.showAuthenticateButton(true); messageHandler.openMessageDialog( @@ -279,7 +281,7 @@ var Toolbar = (function (my) { */ my.openLockDialog = function () { // Only the focus is able to set a shared key. - if (!Moderator.isModerator()) { + if (!xmpp.isModerator()) { if (sharedKey) { messageHandler.openMessageDialog(null, "This conversation is currently protected by" + @@ -436,14 +438,14 @@ var Toolbar = (function (my) { */ my.unlockLockButton = function () { if ($("#lockIcon").hasClass("icon-security-locked")) - buttonClick("#lockIcon", "icon-security icon-security-locked"); + UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked"); }; /** * Updates the lock button state to locked. */ my.lockLockButton = function () { if ($("#lockIcon").hasClass("icon-security")) - buttonClick("#lockIcon", "icon-security icon-security-locked"); + UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked"); }; /** @@ -486,7 +488,7 @@ var Toolbar = (function (my) { // Shows or hides SIP calls button my.showSipCallButton = function (show) { - if (Moderator.isSipGatewayEnabled() && show) { + if (xmpp.isSipGatewayEnabled() && show) { $('#sipCallButton').css({display: "inline"}); } else { $('#sipCallButton').css({display: "none"}); diff --git a/modules/UI/toolbars/ToolbarToggler.js b/modules/UI/toolbars/ToolbarToggler.js index 6a5655702..f64664684 100644 --- a/modules/UI/toolbars/ToolbarToggler.js +++ b/modules/UI/toolbars/ToolbarToggler.js @@ -67,7 +67,7 @@ var ToolbarToggler = { toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT; } - if (Moderator.isModerator()) + if (xmpp.isModerator()) { // TODO: Enable settings functionality. // Need to uncomment the settings button in index.html. diff --git a/modules/UI/util/UIUtil.js b/modules/UI/util/UIUtil.js index fa0f4f87c..efb8a2b7b 100644 --- a/modules/UI/util/UIUtil.js +++ b/modules/UI/util/UIUtil.js @@ -11,6 +11,13 @@ module.exports = { = PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0; return window.innerWidth - rightPanelWidth; + }, + /** + * Changes the style class of the element given by id. + */ + buttonClick: function(id, classname) { + $(id).toggleClass(classname); // add the class to the clicked element } + }; \ No newline at end of file diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 6061055a6..024755dd4 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -16,8 +16,99 @@ var largeVideoState = { newSrc: '' }; +/** + * Indicates if we have muted our audio before the conference has started. + * @type {boolean} + */ +var preMuted = false; + +var mutedAudios = {}; + +var flipXLocalVideo = true; +var currentVideoWidth = null; +var currentVideoHeight = null; + +var localVideoSrc = null; + var defaultLocalDisplayName = "Me"; +function videoactive( videoelem) { + if (videoelem.attr('id').indexOf('mixedmslabel') === -1) { + // ignore mixedmslabela0 and v0 + + videoelem.show(); + VideoLayout.resizeThumbnails(); + + var videoParent = videoelem.parent(); + var parentResourceJid = null; + if (videoParent) + parentResourceJid + = VideoLayout.getPeerContainerResourceJid(videoParent[0]); + + // Update the large video to the last added video only if there's no + // current dominant, focused speaker or prezi playing or update it to + // the current dominant speaker. + if ((!focusedVideoInfo && + !VideoLayout.getDominantSpeakerResourceJid() && + !require("../prezi/Prezi").isPresentationVisible()) || + (parentResourceJid && + VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) { + VideoLayout.updateLargeVideo( + RTC.getVideoSrc(videoelem[0]), + 1, + parentResourceJid); + } + + VideoLayout.showModeratorIndicator(); + } +} + +function waitForRemoteVideo(selector, ssrc, stream, jid) { + // XXX(gp) so, every call to this function is *always* preceded by a call + // to the RTC.attachMediaStream() function but that call is *not* followed + // by an update to the videoSrcToSsrc map! + // + // The above way of doing things results in video SRCs that don't correspond + // to any SSRC for a short period of time (to be more precise, for as long + // the waitForRemoteVideo takes to complete). This causes problems (see + // bellow). + // + // I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream() + // a second time in here and only then update the videoSrcToSsrc map? Why + // not simply update the videoSrcToSsrc map when the RTC.attachMediaStream() + // is called the first time? I actually do that in the lastN changed event + // handler because the "orphan" video SRC is causing troubles there. The + // purpose of this method would then be to fire the "videoactive.jingle". + // + // Food for though I guess :-) + + if (selector.removed || !selector.parent().is(":visible")) { + console.warn("Media removed before had started", selector); + return; + } + + if (stream.id === 'mixedmslabel') return; + + if (selector[0].currentTime > 0) { + var videoStream = simulcast.getReceivingVideoStream(stream); + RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF? + + // FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type + // in order to get rid of too many maps + if (ssrc && jid) { + jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc; + } else { + console.warn("No ssrc given for jid", jid); + } + + videoactive(selector); + } else { + setTimeout(function () { + waitForRemoteVideo(selector, ssrc, stream, jid); + }, 250); + } +} + /** * Returns an array of the video horizontal and vertical indents, * so that if fits its parent. @@ -194,7 +285,7 @@ function getParticipantContainer(resourceJid) if (!resourceJid) return null; - if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)) + if (resourceJid === xmpp.myResource()) return $("#localVideoContainer"); else return $("#participant_" + resourceJid); @@ -270,7 +361,8 @@ function addRemoteVideoMenu(jid, parentElement) { event.preventDefault(); } var isMute = mutedAudios[jid] == true; - connection.moderate.setMute(jid, !isMute); + xmpp.setMute(jid, !isMute); + popupmenuElement.setAttribute('style', 'display:none;'); if (isMute) { @@ -292,7 +384,7 @@ function addRemoteVideoMenu(jid, parentElement) { var ejectLinkItem = document.createElement('a'); ejectLinkItem.innerHTML = ejectIndicator + ' Kick out'; ejectLinkItem.onclick = function(){ - connection.moderate.eject(jid); + xmpp.eject(jid); popupmenuElement.setAttribute('style', 'display:none;'); }; @@ -400,6 +492,43 @@ function createModeratorIndicatorElement(parentElement) { } +/** + * Checks if video identified by given src is desktop stream. + * @param videoSrc eg. + * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395 + * @returns {boolean} + */ +function isVideoSrcDesktop(jid) { + // FIXME: fix this mapping mess... + // figure out if large video is desktop stream or just a camera + + if(!jid) + return false; + var isDesktop = false; + if (xmpp.myJid() && + xmpp.myResource() === jid) { + // local video + isDesktop = desktopsharing.isUsingScreenStream(); + } else { + // Do we have associations... + var videoSsrc = jid2Ssrc[jid]; + if (videoSsrc) { + var videoType = ssrc2videoType[videoSsrc]; + if (videoType) { + // Finally there... + isDesktop = videoType === 'screen'; + } else { + console.error("No video type for ssrc: " + videoSsrc); + } + } else { + console.error("No ssrc for jid: " + jid); + } + } + return isDesktop; +} + + + var VideoLayout = (function (my) { my.connectionIndicators = {}; @@ -407,6 +536,16 @@ var VideoLayout = (function (my) { my.getVideoSize = getCameraVideoSize; my.getVideoPosition = getCameraVideoPosition; + my.init = function () { + // Listen for large video size updates + document.getElementById('largeVideo') + .addEventListener('loadedmetadata', function (e) { + currentVideoWidth = this.videoWidth; + currentVideoHeight = this.videoHeight; + VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight); + }); + }; + my.isInLastN = function(resource) { return lastNCount < 0 // lastN is disabled, return true || (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true @@ -422,7 +561,10 @@ var VideoLayout = (function (my) { document.getElementById('localAudio').autoplay = true; document.getElementById('localAudio').volume = 0; if (preMuted) { - setAudioMuted(true); + if(!UI.setAudioMuted(true)) + { + preMuted = mute; + } preMuted = false; } }; @@ -459,14 +601,14 @@ var VideoLayout = (function (my) { VideoLayout.handleVideoThumbClicked( RTC.getVideoSrc(localVideo), false, - Strophe.getResourceFromJid(connection.emuc.myroomjid)); + xmpp.myResource()); }); $('#localVideoContainer').click(function (event) { event.stopPropagation(); VideoLayout.handleVideoThumbClicked( RTC.getVideoSrc(localVideo), false, - Strophe.getResourceFromJid(connection.emuc.myroomjid)); + xmpp.myResource()); }); // Add hover handler @@ -496,11 +638,8 @@ var VideoLayout = (function (my) { localVideoSrc = RTC.getVideoSrc(localVideo); - var myResourceJid = null; - if(connection.emuc.myroomjid) - { - myResourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid); - } + var myResourceJid = xmpp.myResource(); + VideoLayout.updateLargeVideo(localVideoSrc, 0, myResourceJid); @@ -539,7 +678,7 @@ var VideoLayout = (function (my) { { if(container.id == "localVideoWrapper") { - jid = Strophe.getResourceFromJid(connection.emuc.myroomjid); + jid = xmpp.myResource(); } else { @@ -617,9 +756,9 @@ var VideoLayout = (function (my) { largeVideoState.isVisible = $('#largeVideo').is(':visible'); largeVideoState.isDesktop = isVideoSrcDesktop(resourceJid); if(jid2Ssrc[largeVideoState.userResourceJid] || - (connection && connection.emuc.myroomjid && + (xmpp.myResource() && largeVideoState.userResourceJid === - Strophe.getResourceFromJid(connection.emuc.myroomjid))) { + xmpp.myResource())) { largeVideoState.oldResourceJid = largeVideoState.userResourceJid; } else { largeVideoState.oldResourceJid = null; @@ -643,7 +782,7 @@ var VideoLayout = (function (my) { var doUpdate = function () { Avatar.updateActiveSpeakerAvatarSrc( - connection.emuc.findJidFromResource( + xmpp.findJidFromResource( largeVideoState.userResourceJid)); if (!userChanged && largeVideoState.preload && @@ -723,7 +862,7 @@ var VideoLayout = (function (my) { if(userChanged) { Avatar.showUserAvatar( - connection.emuc.findJidFromResource( + xmpp.findJidFromResource( largeVideoState.oldResourceJid)); } @@ -738,7 +877,7 @@ var VideoLayout = (function (my) { } } else { Avatar.showUserAvatar( - connection.emuc.findJidFromResource( + xmpp.findJidFromResource( largeVideoState.userResourceJid)); } @@ -877,7 +1016,7 @@ var VideoLayout = (function (my) { focusedVideoInfo = null; if(focusResourceJid) { Avatar.showUserAvatar( - connection.emuc.findJidFromResource(focusResourceJid)); + xmpp.findJidFromResource(focusResourceJid)); } } } @@ -949,7 +1088,7 @@ var VideoLayout = (function (my) { // If the peerJid is null then this video span couldn't be directly // associated with a participant (this could happen in the case of prezi). - if (Moderator.isModerator() && peerJid !== null) + if (xmpp.isModerator() && peerJid !== null) addRemoteVideoMenu(peerJid, container); remotes.appendChild(container); @@ -1134,13 +1273,13 @@ var VideoLayout = (function (my) { if (state == 'show') { // peerContainer.css('-webkit-filter', ''); - var jid = connection.emuc.findJidFromResource(resourceJid); + var jid = xmpp.findJidFromResource(resourceJid); Avatar.showUserAvatar(jid, false); } else // if (state == 'avatar') { // peerContainer.css('-webkit-filter', 'grayscale(100%)'); - var jid = connection.emuc.findJidFromResource(resourceJid); + var jid = xmpp.findJidFromResource(resourceJid); Avatar.showUserAvatar(jid, true); } } @@ -1166,8 +1305,7 @@ var VideoLayout = (function (my) { if (name && nickname !== name) { nickname = name; window.localStorage.displayname = nickname; - connection.emuc.addDisplayNameToPresence(nickname); - connection.emuc.sendPresence(); + xmpp.addToPresence("displayName", nickname); Chat.setChatConversationMode(true); } @@ -1238,7 +1376,7 @@ var VideoLayout = (function (my) { */ my.showModeratorIndicator = function () { - var isModerator = Moderator.isModerator(); + var isModerator = xmpp.isModerator(); if (isModerator) { var indicatorSpan = $('#localVideoContainer .focusindicator'); @@ -1247,7 +1385,10 @@ var VideoLayout = (function (my) { createModeratorIndicatorElement(indicatorSpan[0]); } } - Object.keys(connection.emuc.members).forEach(function (jid) { + + var members = xmpp.getMembers(); + + Object.keys(members).forEach(function (jid) { if (Strophe.getResourceFromJid(jid) === 'focus') { // Skip server side focus @@ -1263,7 +1404,7 @@ var VideoLayout = (function (my) { return; } - var member = connection.emuc.members[jid]; + var member = members[jid]; if (member.role === 'moderator') { // Remove menu if peer is moderator @@ -1435,7 +1576,7 @@ var VideoLayout = (function (my) { var videoSpanId = null; var videoContainerId = null; if (resourceJid - === Strophe.getResourceFromJid(connection.emuc.myroomjid)) { + === xmpp.myResource()) { videoSpanId = 'localVideoWrapper'; videoContainerId = 'localVideoContainer'; } @@ -1478,7 +1619,7 @@ var VideoLayout = (function (my) { } Avatar.showUserAvatar( - connection.emuc.findJidFromResource(resourceJid)); + xmpp.findJidFromResource(resourceJid)); } }; @@ -1603,7 +1744,7 @@ var VideoLayout = (function (my) { lastNPickupJid = jid; $(document).trigger("pinnedendpointchanged", [jid]); } - } else if (jid == connection.emuc.myroomjid) { + } else if (jid == xmpp.myJid()) { $("#localVideoContainer").click(); } } @@ -1615,13 +1756,13 @@ var VideoLayout = (function (my) { $(document).bind('audiomuted.muc', function (event, jid, isMuted) { /* // FIXME: but focus can not mute in this case ? - check - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { // The local mute indicator is controlled locally return; }*/ var videoSpanId = null; - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { videoSpanId = 'localVideoContainer'; } else { VideoLayout.ensurePeerContainerExists(jid); @@ -1630,7 +1771,7 @@ var VideoLayout = (function (my) { mutedAudios[jid] = isMuted; - if (Moderator.isModerator()) { + if (xmpp.isModerator()) { VideoLayout.updateRemoteVideoMenu(jid, isMuted); } @@ -1648,7 +1789,7 @@ var VideoLayout = (function (my) { Avatar.showUserAvatar(jid, isMuted); var videoSpanId = null; - if (jid === connection.emuc.myroomjid) { + if (jid === xmpp.myJid()) { videoSpanId = 'localVideoContainer'; } else { VideoLayout.ensurePeerContainerExists(jid); @@ -1662,11 +1803,11 @@ var VideoLayout = (function (my) { /** * Display name changed. */ - $(document).bind('displaynamechanged', - function (event, jid, displayName, status) { + my.onDisplayNameChanged = + function (jid, displayName, status) { var name = null; if (jid === 'localVideoContainer' - || jid === connection.emuc.myroomjid) { + || jid === xmpp.myJid()) { name = nickname; setDisplayName('localVideoContainer', displayName); @@ -1680,10 +1821,10 @@ var VideoLayout = (function (my) { } if(jid === 'localVideoContainer') - jid = connection.emuc.myroomjid; + jid = xmpp.myJid(); if(!name || name != displayName) API.triggerEvent("displayNameChange",{jid: jid, displayname: displayName}); - }); + }; /** * On dominant speaker changed event. @@ -1691,7 +1832,7 @@ var VideoLayout = (function (my) { $(document).bind('dominantspeakerchanged', function (event, resourceJid) { // We ignore local user events. if (resourceJid - === Strophe.getResourceFromJid(connection.emuc.myroomjid)) + === xmpp.myResource()) return; // Update the current dominant speaker. @@ -1822,7 +1963,7 @@ var VideoLayout = (function (my) { if (!isVisible) { console.log("Add to last N", resourceJid); - var jid = connection.emuc.findJidFromResource(resourceJid); + var jid = xmpp.findJidFromResource(resourceJid); var mediaStream = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; var sel = $('#participant_' + resourceJid + '>video'); @@ -1855,7 +1996,7 @@ var VideoLayout = (function (my) { var resource, container, src; var myResource - = Strophe.getResourceFromJid(connection.emuc.myroomjid); + = xmpp.myResource(); // Find out which endpoint to show in the large video. for (var i = 0; i < lastNEndpoints.length; i++) { @@ -1879,37 +2020,6 @@ var VideoLayout = (function (my) { } }); - $(document).bind('videoactive.jingle', function (event, videoelem) { - if (videoelem.attr('id').indexOf('mixedmslabel') === -1) { - // ignore mixedmslabela0 and v0 - - videoelem.show(); - VideoLayout.resizeThumbnails(); - - var videoParent = videoelem.parent(); - var parentResourceJid = null; - if (videoParent) - parentResourceJid - = VideoLayout.getPeerContainerResourceJid(videoParent[0]); - - // Update the large video to the last added video only if there's no - // current dominant, focused speaker or prezi playing or update it to - // the current dominant speaker. - if ((!focusedVideoInfo && - !VideoLayout.getDominantSpeakerResourceJid() && - !require("../prezi/Prezi").isPresentationVisible()) || - (parentResourceJid && - VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) { - VideoLayout.updateLargeVideo( - RTC.getVideoSrc(videoelem[0]), - 1, - parentResourceJid); - } - - VideoLayout.showModeratorIndicator(); - } - }); - $(document).bind('simulcastlayerschanging', function (event, endpointSimulcastLayers) { endpointSimulcastLayers.forEach(function (esl) { @@ -1930,13 +2040,13 @@ var VideoLayout = (function (my) { // Get session and stream from primary ssrc. var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC); - var session = res.session; + var sid = res.sid; var electedStream = res.stream; - if (session && electedStream) { + if (sid && electedStream) { var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC); - console.info([esl, primarySSRC, msid, session, electedStream]); + console.info([esl, primarySSRC, msid, sid, electedStream]); var msidParts = msid.split(' '); @@ -1956,7 +2066,7 @@ var VideoLayout = (function (my) { } } else { - console.error('Could not find a stream or a session.', session, electedStream); + console.error('Could not find a stream or a session.', sid, electedStream); } }); }); @@ -1988,17 +2098,17 @@ var VideoLayout = (function (my) { // Get session and stream from primary ssrc. var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC); - var session = res.session; + var sid = res.sid; var electedStream = res.stream; - if (session && electedStream) { + if (sid && electedStream) { var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC); console.info('Switching simulcast substream.'); - console.info([esl, primarySSRC, msid, session, electedStream]); + console.info([esl, primarySSRC, msid, sid, electedStream]); var msidParts = msid.split(' '); - var selRemoteVideo = $(['#', 'remoteVideo_', session.sid, '_', msidParts[0]].join('')); + var selRemoteVideo = $(['#', 'remoteVideo_', sid, '_', msidParts[0]].join('')); var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC]) == largeVideoState.userResourceJid); @@ -2035,7 +2145,7 @@ var VideoLayout = (function (my) { } var videoId; - if(resource == Strophe.getResourceFromJid(connection.emuc.myroomjid)) + if(resource == xmpp.myResource()) { videoId = "localVideoContainer"; } @@ -2048,7 +2158,7 @@ var VideoLayout = (function (my) { connectionIndicator.updatePopoverData(); } else { - console.error('Could not find a stream or a session.', session, electedStream); + console.error('Could not find a stream or a sid.', sid, electedStream); } }); }); @@ -2063,8 +2173,8 @@ var VideoLayout = (function (my) { if(object.resolution !== null) { resolution = object.resolution; - object.resolution = resolution[connection.emuc.myroomjid]; - delete resolution[connection.emuc.myroomjid]; + object.resolution = resolution[xmpp.myJid()]; + delete resolution[xmpp.myJid()]; } updateStatsIndicator("localVideoContainer", percent, object); for(var jid in resolution) diff --git a/modules/connectionquality/connectionquality.js b/modules/connectionquality/connectionquality.js index 4a668a002..7652caec6 100644 --- a/modules/connectionquality/connectionquality.js +++ b/modules/connectionquality/connectionquality.js @@ -29,8 +29,7 @@ function startSendingStats() { * Sends statistics to other participants */ function sendStats() { - connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats)); - connection.emuc.sendPresence(); + xmpp.addToPresence("connectionQuality", convertToMUCStats(stats)); } /** diff --git a/modules/desktopsharing/desktopsharing.js b/modules/desktopsharing/desktopsharing.js index ebc943ae8..42b633f9d 100644 --- a/modules/desktopsharing/desktopsharing.js +++ b/modules/desktopsharing/desktopsharing.js @@ -1,4 +1,4 @@ -/* global $, alert, changeLocalVideo, chrome, config, connection, getConferenceHandler, getUserMediaWithConstraints */ +/* global $, alert, changeLocalVideo, chrome, config, getConferenceHandler, getUserMediaWithConstraints */ /** * Indicates that desktop stream is currently in use(for toggle purpose). * @type {boolean} diff --git a/modules/simulcast/SimulcastReceiver.js b/modules/simulcast/SimulcastReceiver.js index 863c5a826..c019a299f 100644 --- a/modules/simulcast/SimulcastReceiver.js +++ b/modules/simulcast/SimulcastReceiver.js @@ -159,43 +159,19 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) { // If we haven't receiving a "changed" event yet, then we must be receiving // low quality (that the sender always streams). - if (!ssrc && connection.jingle) { - var session; - var i, j, k; - - var keys = Object.keys(connection.jingle.sessions); - for (i = 0; i < keys.length; i++) { - var sid = keys[i]; - - if (ssrc) { - // stream found, stop. - break; - } - - session = connection.jingle.sessions[sid]; - if (session.remoteStreams) { - for (j = 0; j < session.remoteStreams.length; j++) { - var remoteStream = session.remoteStreams[j]; - - if (ssrc) { - // stream found, stop. - break; - } - var tracks = remoteStream.getVideoTracks(); - if (tracks) { - for (k = 0; k < tracks.length; k++) { - var track = tracks[k]; - var msid = [remoteStream.id, track.id].join(' '); - var _ssrc = this._remoteMaps.msid2ssrc[msid]; - var _jid = ssrc2jid[_ssrc]; - var quality = this._remoteMaps.msid2Quality[msid]; - if (jid == _jid && quality == 0) { - ssrc = _ssrc; - // stream found, stop. - break; - } - } - } + if(!ssrc) + { + var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; + var remoteStream = remoteStreamObject.getOriginalStream(); + var tracks = remoteStream.getVideoTracks(); + if (tracks) { + for (var k = 0; k < tracks.length; k++) { + var track = tracks[k]; + var msid = [remoteStream.id, track.id].join(' '); + var _ssrc = this._remoteMaps.msid2ssrc[msid]; + var quality = this._remoteMaps.msid2Quality[msid]; + if (quality == 0) { + ssrc = _ssrc; } } } @@ -206,47 +182,32 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) { SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc) { - var session, electedStream; + var sid, electedStream; var i, j, k; - if (connection.jingle) { - var keys = Object.keys(connection.jingle.sessions); - for (i = 0; i < keys.length; i++) { - var sid = keys[i]; - - if (electedStream) { - // stream found, stop. - break; - } - - session = connection.jingle.sessions[sid]; - if (session.remoteStreams) { - for (j = 0; j < session.remoteStreams.length; j++) { - var remoteStream = session.remoteStreams[j]; - - if (electedStream) { - // stream found, stop. - break; - } - var tracks = remoteStream.getVideoTracks(); - if (tracks) { - for (k = 0; k < tracks.length; k++) { - var track = tracks[k]; - var msid = [remoteStream.id, track.id].join(' '); - var tmp = this._remoteMaps.msid2ssrc[msid]; - if (tmp == ssrc) { - electedStream = new webkitMediaStream([track]); - // stream found, stop. - break; - } - } - } + var jid = ssrc2jid[ssrc]; + if(jid) + { + var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]; + var remoteStream = remoteStreamObject.getOriginalStream(); + var tracks = remoteStream.getVideoTracks(); + if (tracks) { + for (k = 0; k < tracks.length; k++) { + var track = tracks[k]; + var msid = [remoteStream.id, track.id].join(' '); + var tmp = this._remoteMaps.msid2ssrc[msid]; + if (tmp == ssrc) { + electedStream = new webkitMediaStream([track]); + sid = remoteStreamObject.sid; + // stream found, stop. + break; } } } + } return { - session: session, + sid: sid, stream: electedStream }; }; diff --git a/modules/statistics/RTPStatsCollector.js b/modules/statistics/RTPStatsCollector.js index 3b070dda1..c7901a23b 100644 --- a/modules/statistics/RTPStatsCollector.js +++ b/modules/statistics/RTPStatsCollector.js @@ -329,30 +329,9 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) { }; StatsCollector.prototype.logStats = function () { - if (!focusMucJid) { + + if(!xmpp.sendLogs(this.statsToBeLogged)) return; - } - - var deflate = true; - - var content = JSON.stringify(this.statsToBeLogged); - if (deflate) { - content = String.fromCharCode.apply(null, Pako.deflateRaw(content)); - } - content = Base64.encode(content); - - // XEP-0337-ish - var message = $msg({to: focusMucJid, type: 'normal'}); - message.c('log', { xmlns: 'urn:xmpp:eventlog', - id: 'PeerConnectionStats'}); - message.c('message').t(content).up(); - if (deflate) { - message.c('tag', {name: "deflated", value: "true"}).up(); - } - message.up(); - - connection.send(message); - // Reset the stats this.statsToBeLogged.stats = {}; this.statsToBeLogged.timestamps = []; @@ -700,7 +679,7 @@ StatsCollector.prototype.processAudioLevelReport = function () // but it seems to vary between 0 and around 32k. audioLevel = audioLevel / 32767; jidStats.setSsrcAudioLevel(ssrc, audioLevel); - if(jid != connection.emuc.myroomjid) + if(jid != xmpp.myJid()) this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel); } diff --git a/modules/statistics/statistics.js b/modules/statistics/statistics.js index 828c401e9..f0449cb7a 100644 --- a/modules/statistics/statistics.js +++ b/modules/statistics/statistics.js @@ -59,6 +59,14 @@ function onStreamCreated(stream) localStats.start(); } +function onDisposeConference(onUnload) { + stopRemote(); + if(onUnload) { + stopLocal(); + eventEmitter.removeAllListeners(); + } +} + var statistics = { @@ -117,19 +125,12 @@ var statistics = startRemoteStats(event.peerconnection); }, - onDisposeConference: function (onUnload) { - stopRemote(); - if(onUnload) { - stopLocal(); - eventEmitter.removeAllListeners(); - } - }, - start: function () { this.addConnectionStatsListener(connectionquality.updateLocalStats); this.addRemoteStatsStopListener(connectionquality.stopSendingStats); RTC.addStreamListener(onStreamCreated, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); + xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference); } }; diff --git a/libs/strophe/strophe.jingle.session.js b/modules/xmpp/JingleSession.js similarity index 83% rename from libs/strophe/strophe.jingle.session.js rename to modules/xmpp/JingleSession.js index 2517520e6..8ff3d0efa 100644 --- a/libs/strophe/strophe.jingle.session.js +++ b/modules/xmpp/JingleSession.js @@ -1,6 +1,11 @@ /* jshint -W117 */ +var TraceablePeerConnection = require("./TraceablePeerConnection"); +var SDPDiffer = require("./SDPDiffer"); +var SDPUtil = require("./SDPUtil"); +var SDP = require("./SDP"); + // Jingle stuff -function JingleSession(me, sid, connection) { +function JingleSession(me, sid, connection, service) { this.me = me; this.sid = sid; this.connection = connection; @@ -12,13 +17,13 @@ function JingleSession(me, sid, connection) { this.localSDP = null; this.remoteSDP = null; this.relayedStreams = []; - this.remoteStreams = []; this.startTime = null; this.stopTime = null; this.media_constraints = null; this.pc_constraints = null; this.ice_config = {}; this.drip_container = []; + this.service = service; this.usetrickle = true; this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718 @@ -73,16 +78,11 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) { self.sendIceCandidate(event.candidate); }; this.peerconnection.onaddstream = function (event) { - self.remoteStreams.push(event.stream); console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id); - $(document).trigger('remotestreamadded.jingle', [event, self.sid]); + self.remoteStreamAdded(event); }; this.peerconnection.onremovestream = function (event) { // Remove the stream from remoteStreams - var streamIdx = self.remoteStreams.indexOf(event.stream); - if(streamIdx !== -1){ - self.remoteStreams.splice(streamIdx, 1); - } // FIXME: remotestreamremoved.jingle not defined anywhere(unused) $(document).trigger('remotestreamremoved.jingle', [event, self.sid]); }; @@ -99,7 +99,7 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) { this.stopTime = new Date(); break; } - $(document).trigger('iceconnectionstatechange.jingle', [self.sid, self]); + onIceConnectionStateChange(self.sid, self); }; // add any local and relayed stream RTC.localStreams.forEach(function(stream) { @@ -110,6 +110,49 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) { }); }; +function onIceConnectionStateChange(sid, session) { + switch (session.peerconnection.iceConnectionState) { + case 'checking': + session.timeChecking = (new Date()).getTime(); + session.firstconnect = true; + break; + case 'completed': // on caller side + case 'connected': + if (session.firstconnect) { + session.firstconnect = false; + var metadata = {}; + metadata.setupTime + = (new Date()).getTime() - session.timeChecking; + session.peerconnection.getStats(function (res) { + if(res && res.result) { + res.result().forEach(function (report) { + if (report.type == 'googCandidatePair' && + report.stat('googActiveConnection') == 'true') { + metadata.localCandidateType + = report.stat('googLocalCandidateType'); + metadata.remoteCandidateType + = report.stat('googRemoteCandidateType'); + + // log pair as well so we can get nice pie + // charts + metadata.candidatePair + = report.stat('googLocalCandidateType') + + ';' + + report.stat('googRemoteCandidateType'); + + if (report.stat('googRemoteAddress').indexOf('[') === 0) + { + metadata.ipv6 = true; + } + } + }); + } + }); + } + break; + } +} + JingleSession.prototype.accept = function () { var self = this; this.state = 'active'; @@ -145,12 +188,13 @@ JingleSession.prototype.accept = function () { // FIXME: change any inactive to sendrecv or whatever they were originally sdp = sdp.replace('a=inactive', 'a=sendrecv'); } + var self = this; this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}), function () { //console.log('setLocalDescription success'); - $(document).trigger('setLocalDescription.jingle', [self.sid]); + self.setLocalDescription(); - this.connection.sendIQ(accept, + self.connection.sendIQ(accept, function () { var ack = {}; ack.source = 'answer'; @@ -347,8 +391,8 @@ JingleSession.prototype.createdOffer = function (sdp) { action: 'session-initiate', initiator: this.initiator, sid: this.sid}); - this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC); - this.connection.sendIQ(init, + self.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC); + self.connection.sendIQ(init, function () { var ack = {}; ack.source = 'offer'; @@ -369,13 +413,11 @@ JingleSession.prototype.createdOffer = function (sdp) { sdp.sdp = this.localSDP.raw; this.peerconnection.setLocalDescription(sdp, function () { - if(this.usetrickle) + if(self.usetrickle) { sendJingle(); - $(document).trigger('setLocalDescription.jingle', [self.sid]); } - else - $(document).trigger('setLocalDescription.jingle', [self.sid]); + self.setLocalDescription(); //console.log('setLocalDescription success'); }, function (e) { @@ -587,7 +629,7 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) { var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp); var publicLocalSDP = new SDP(publicLocalDesc.sdp); publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs); - this.connection.sendIQ(accept, + self.connection.sendIQ(accept, function () { var ack = {}; ack.source = 'answer'; @@ -610,10 +652,8 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) { //console.log('setLocalDescription success'); if (self.usetrickle && !self.usepranswer) { sendJingle(); - $(document).trigger('setLocalDescription.jingle', [self.sid]); } - else - $(document).trigger('setLocalDescription.jingle', [self.sid]); + self.setLocalDescription(); }, function (e) { console.error('setLocalDescription failed', e); @@ -799,7 +839,7 @@ JingleSession.prototype.modifySources = function (successCallback) { if (this.peerconnection.signalingState == 'closed') return; if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){ // There is nothing to do since scheduled job might have been executed by another succeeding call - $(document).trigger('setLocalDescription.jingle', [self.sid]); + this.setLocalDescription(); if(successCallback){ successCallback(); } @@ -889,7 +929,7 @@ JingleSession.prototype.modifySources = function (successCallback) { self.peerconnection.setLocalDescription(modifiedAnswer, function() { //console.log('modified setLocalDescription ok'); - $(document).trigger('setLocalDescription.jingle', [self.sid]); + self.setLocalDescription(); if(successCallback){ successCallback(); } @@ -1064,12 +1104,20 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) { } else if (this.videoMuteByUser) { return; } + + var self = this; + var localCallback = function (mute) { + self.connection.emuc.addVideoInfoToPresence(mute); + self.connection.emuc.sendPresence(); + return callback(mute) + }; + if (mute == RTC.localVideo.isMuted()) { // Even if no change occurs, the specified callback is to be executed. // The specified callback may, optionally, return a successCallback // which is to be executed as well. - var successCallback = callback(mute); + var successCallback = localCallback(mute); if (successCallback) { successCallback(); @@ -1079,14 +1127,14 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) { this.hardMuteVideo(mute); - this.modifySources(callback(mute)); + this.modifySources(localCallback(mute)); } }; // SDP-based mute by going recvonly/sendrecv // FIXME: should probably black out the screen as well JingleSession.prototype.toggleVideoMute = function (callback) { - setVideoMute(RTC.localVideo.isMuted(), callback); + this.service.setVideoMute(RTC.localVideo.isMuted(), callback); }; JingleSession.prototype.hardMuteVideo = function (muted) { @@ -1172,8 +1220,170 @@ JingleSession.onJingleError = function (session, error) JingleSession.onJingleFatalError = function (session, error) { - sessionTerminated = true; + this.service.sessionTerminated = true; connection.emuc.doLeave(); UI.messageHandler.showError( "Sorry", "Internal application error[setRemoteDescription]"); -} \ No newline at end of file +} + +JingleSession.prototype.setLocalDescription = function () { + // put our ssrcs into presence so other clients can identify our stream + var newssrcs = []; + var media = simulcast.parseMedia(this.peerconnection.localDescription); + media.forEach(function (media) { + + if(Object.keys(media.sources).length > 0) { + // TODO(gp) maybe exclude FID streams? + Object.keys(media.sources).forEach(function (ssrc) { + newssrcs.push({ + 'ssrc': ssrc, + 'type': media.type, + 'direction': media.direction + }); + }); + } + else if(this.localStreamsSSRC && this.localStreamsSSRC[media.type]) + { + newssrcs.push({ + 'ssrc': this.localStreamsSSRC[media.type], + 'type': media.type, + 'direction': media.direction + }); + } + + }); + + console.log('new ssrcs', newssrcs); + + // Have to clear presence map to get rid of removed streams + this.connection.emuc.clearPresenceMedia(); + + if (newssrcs.length > 0) { + for (var i = 1; i <= newssrcs.length; i ++) { + // Change video type to screen + if (newssrcs[i-1].type === 'video' && desktopsharing.isUsingScreenStream()) { + newssrcs[i-1].type = 'screen'; + } + this.connection.emuc.addMediaToPresence(i, + newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction); + } + + this.connection.emuc.sendPresence(); + } +} + +// an attempt to work around https://github.com/jitsi/jitmeet/issues/32 +function sendKeyframe(pc) { + console.log('sendkeyframe', pc.iceConnectionState); + if (pc.iceConnectionState !== 'connected') return; // safe... + pc.setRemoteDescription( + pc.remoteDescription, + function () { + pc.createAnswer( + function (modifiedAnswer) { + pc.setLocalDescription( + modifiedAnswer, + function () { + // noop + }, + function (error) { + console.log('triggerKeyframe setLocalDescription failed', error); + UI.messageHandler.showError(); + } + ); + }, + function (error) { + console.log('triggerKeyframe createAnswer failed', error); + UI.messageHandler.showError(); + } + ); + }, + function (error) { + console.log('triggerKeyframe setRemoteDescription failed', error); + UI.messageHandler.showError(); + } + ); +} + + +JingleSession.prototype.remoteStreamAdded = function (data) { + var self = this; + var thessrc; + + // look up an associated JID for a stream id + if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) { + // look only at a=ssrc: and _not_ at a=ssrc-group: lines + + var ssrclines + = SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:'); + ssrclines = ssrclines.filter(function (line) { + // NOTE(gp) previously we filtered on the mslabel, but that property + // is not always present. + // return line.indexOf('mslabel:' + data.stream.label) !== -1; + + return ((line.indexOf('msid:' + data.stream.id) !== -1)); + }); + if (ssrclines.length) { + thessrc = ssrclines[0].substring(7).split(' ')[0]; + + // We signal our streams (through Jingle to the focus) before we set + // our presence (through which peers associate remote streams to + // jids). So, it might arrive that a remote stream is added but + // ssrc2jid is not yet updated and thus data.peerjid cannot be + // successfully set. Here we wait for up to a second for the + // presence to arrive. + + if (!ssrc2jid[thessrc]) { + // TODO(gp) limit wait duration to 1 sec. + setTimeout(function(d) { + return function() { + self.remoteStreamAdded(d); + } + }(data), 250); + return; + } + + // ok to overwrite the one from focus? might save work in colibri.js + console.log('associated jid', ssrc2jid[thessrc], data.peerjid); + if (ssrc2jid[thessrc]) { + data.peerjid = ssrc2jid[thessrc]; + } + } + } + + //TODO: this code should be removed when firefox implement multistream support + if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX) + { + if((notReceivedSSRCs.length == 0) || + !ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]]) + { + // TODO(gp) limit wait duration to 1 sec. + setTimeout(function(d) { + return function() { + self.remoteStreamAdded(d); + } + }(data), 250); + return; + } + + thessrc = notReceivedSSRCs.pop(); + if (ssrc2jid[thessrc]) { + data.peerjid = ssrc2jid[thessrc]; + } + } + + RTC.createRemoteStream(data, this.sid, thessrc); + + var isVideo = data.stream.getVideoTracks().length > 0; + // an attempt to work around https://github.com/jitsi/jitmeet/issues/32 + if (isVideo && + data.peerjid && this.peerjid === data.peerjid && + data.stream.getVideoTracks().length === 0 && + RTC.localVideo.getTracks().length > 0) { + window.setTimeout(function () { + sendKeyframe(self.peerconnection); + }, 3000); + } +} + +module.exports = JingleSession; \ No newline at end of file diff --git a/libs/strophe/strophe.jingle.sdp.js b/modules/xmpp/SDP.js similarity index 55% rename from libs/strophe/strophe.jingle.sdp.js rename to modules/xmpp/SDP.js index 03dbceb08..4103fbe13 100644 --- a/libs/strophe/strophe.jingle.sdp.js +++ b/modules/xmpp/SDP.js @@ -1,4 +1,6 @@ /* jshint -W117 */ +var SDPUtil = require("./SDPUtil"); + // SDP STUFF function SDP(sdp) { this.media = sdp.split('\r\nm='); @@ -71,169 +73,6 @@ SDP.prototype.containsSSRC = function(ssrc) { return contains; }; -function SDPDiffer(mySDP, otherSDP) { - this.mySDP = mySDP; - this.otherSDP = otherSDP; -} - -/** - * Returns map of MediaChannel that contains only media not contained in otherSdp. Mapped by channel idx. - * @param otherSdp the other SDP to check ssrc with. - */ -SDPDiffer.prototype.getNewMedia = function() { - - // this could be useful in Array.prototype. - function arrayEquals(array) { - // if the other array is a falsy value, return - if (!array) - return false; - - // compare lengths - can save a lot of time - if (this.length != array.length) - return false; - - for (var i = 0, l=this.length; i < l; i++) { - // Check if we have nested arrays - if (this[i] instanceof Array && array[i] instanceof Array) { - // recurse into the nested arrays - if (!this[i].equals(array[i])) - return false; - } - else if (this[i] != array[i]) { - // Warning - two different object instances will never be equal: {x:20} != {x:20} - return false; - } - } - return true; - } - - var myMedias = this.mySDP.getMediaSsrcMap(); - var othersMedias = this.otherSDP.getMediaSsrcMap(); - var newMedia = {}; - Object.keys(othersMedias).forEach(function(othersMediaIdx) { - var myMedia = myMedias[othersMediaIdx]; - var othersMedia = othersMedias[othersMediaIdx]; - if(!myMedia && othersMedia) { - // Add whole channel - newMedia[othersMediaIdx] = othersMedia; - return; - } - // Look for new ssrcs accross the channel - Object.keys(othersMedia.ssrcs).forEach(function(ssrc) { - if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) { - // Allocate channel if we've found ssrc that doesn't exist in our channel - if(!newMedia[othersMediaIdx]){ - newMedia[othersMediaIdx] = { - mediaindex: othersMedia.mediaindex, - mid: othersMedia.mid, - ssrcs: {}, - ssrcGroups: [] - }; - } - newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc]; - } - }); - - // Look for new ssrc groups across the channels - othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){ - - // try to match the other ssrc-group with an ssrc-group of ours - var matched = false; - for (var i = 0; i < myMedia.ssrcGroups.length; i++) { - var mySsrcGroup = myMedia.ssrcGroups[i]; - if (otherSsrcGroup.semantics == mySsrcGroup.semantics - && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) { - - matched = true; - break; - } - } - - if (!matched) { - // Allocate channel if we've found an ssrc-group that doesn't - // exist in our channel - - if(!newMedia[othersMediaIdx]){ - newMedia[othersMediaIdx] = { - mediaindex: othersMedia.mediaindex, - mid: othersMedia.mid, - ssrcs: {}, - ssrcGroups: [] - }; - } - newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup); - } - }); - }); - return newMedia; -}; - -/** - * Sends SSRC update IQ. - * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove. - * @param sid session identifier that will be put into the IQ. - * @param initiator initiator identifier. - * @param toJid destination Jid - * @param isAdd indicates if this is remove or add operation. - */ -SDPDiffer.prototype.toJingle = function(modify) { - var sdpMediaSsrcs = this.getNewMedia(); - var self = this; - - // FIXME: only announce video ssrcs since we mix audio and dont need - // the audio ssrcs therefore - var modified = false; - Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){ - modified = true; - var media = sdpMediaSsrcs[mediaindex]; - modify.c('content', {name: media.mid}); - - modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid}); - // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly - // generate sources from lines - Object.keys(media.ssrcs).forEach(function(ssrcNum) { - var mediaSsrc = media.ssrcs[ssrcNum]; - modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); - modify.attrs({ssrc: mediaSsrc.ssrc}); - // iterate over ssrc lines - mediaSsrc.lines.forEach(function (line) { - var idx = line.indexOf(' '); - var kv = line.substr(idx + 1); - modify.c('parameter'); - if (kv.indexOf(':') == -1) { - modify.attrs({ name: kv }); - } else { - modify.attrs({ name: kv.split(':', 2)[0] }); - modify.attrs({ value: kv.split(':', 2)[1] }); - } - modify.up(); // end of parameter - }); - modify.up(); // end of source - }); - - // generate source groups from lines - media.ssrcGroups.forEach(function(ssrcGroup) { - if (ssrcGroup.ssrcs.length != 0) { - - modify.c('ssrc-group', { - semantics: ssrcGroup.semantics, - xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' - }); - - ssrcGroup.ssrcs.forEach(function (ssrc) { - modify.c('source', { ssrc: ssrc }) - .up(); // end of source - }); - modify.up(); // end of ssrc-group - } - }); - - modify.up(); // end of description - modify.up(); // end of content - }); - - return modified; -}; // remove iSAC and CN from SDP SDP.prototype.mangle = function () { @@ -776,352 +615,6 @@ SDP.prototype.jingle2media = function (content) { return media; }; -SDPUtil = { - iceparams: function (mediadesc, sessiondesc) { - var data = null; - if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) && - SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) { - data = { - ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)), - pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) - }; - } - return data; - }, - parse_iceufrag: function (line) { - return line.substring(12); - }, - build_iceufrag: function (frag) { - return 'a=ice-ufrag:' + frag; - }, - parse_icepwd: function (line) { - return line.substring(10); - }, - build_icepwd: function (pwd) { - return 'a=ice-pwd:' + pwd; - }, - parse_mid: function (line) { - return line.substring(6); - }, - parse_mline: function (line) { - var parts = line.substring(2).split(' '), - data = {}; - data.media = parts.shift(); - data.port = parts.shift(); - data.proto = parts.shift(); - if (parts[parts.length - 1] === '') { // trailing whitespace - parts.pop(); - } - data.fmt = parts; - return data; - }, - build_mline: function (mline) { - return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' '); - }, - parse_rtpmap: function (line) { - var parts = line.substring(9).split(' '), - data = {}; - data.id = parts.shift(); - parts = parts[0].split('/'); - data.name = parts.shift(); - data.clockrate = parts.shift(); - data.channels = parts.length ? parts.shift() : '1'; - return data; - }, - /** - * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it. - * @param line eg. "a=sctpmap:5000 webrtc-datachannel" - * @returns [SCTP port number, protocol, streams] - */ - parse_sctpmap: function (line) - { - var parts = line.substring(10).split(' '); - var sctpPort = parts[0]; - var protocol = parts[1]; - // Stream count is optional - var streamCount = parts.length > 2 ? parts[2] : null; - return [sctpPort, protocol, streamCount];// SCTP port - }, - build_rtpmap: function (el) { - var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate'); - if (el.getAttribute('channels') && el.getAttribute('channels') != '1') { - line += '/' + el.getAttribute('channels'); - } - return line; - }, - parse_crypto: function (line) { - var parts = line.substring(9).split(' '), - data = {}; - data.tag = parts.shift(); - data['crypto-suite'] = parts.shift(); - data['key-params'] = parts.shift(); - if (parts.length) { - data['session-params'] = parts.join(' '); - } - return data; - }, - parse_fingerprint: function (line) { // RFC 4572 - var parts = line.substring(14).split(' '), - data = {}; - data.hash = parts.shift(); - data.fingerprint = parts.shift(); - // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ? - return data; - }, - parse_fmtp: function (line) { - var parts = line.split(' '), - i, key, value, - data = []; - parts.shift(); - parts = parts.join(' ').split(';'); - for (i = 0; i < parts.length; i++) { - key = parts[i].split('=')[0]; - while (key.length && key[0] == ' ') { - key = key.substring(1); - } - value = parts[i].split('=')[1]; - if (key && value) { - data.push({name: key, value: value}); - } else if (key) { - // rfc 4733 (DTMF) style stuff - data.push({name: '', value: key}); - } - } - return data; - }, - parse_icecandidate: function (line) { - var candidate = {}, - elems = line.split(' '); - candidate.foundation = elems[0].substring(12); - candidate.component = elems[1]; - candidate.protocol = elems[2].toLowerCase(); - candidate.priority = elems[3]; - candidate.ip = elems[4]; - candidate.port = elems[5]; - // elems[6] => "typ" - candidate.type = elems[7]; - candidate.generation = 0; // default value, may be overwritten below - for (var i = 8; i < elems.length; i += 2) { - switch (elems[i]) { - case 'raddr': - candidate['rel-addr'] = elems[i + 1]; - break; - case 'rport': - candidate['rel-port'] = elems[i + 1]; - break; - case 'generation': - candidate.generation = elems[i + 1]; - break; - case 'tcptype': - candidate.tcptype = elems[i + 1]; - break; - default: // TODO - console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"'); - } - } - candidate.network = '1'; - candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random - return candidate; - }, - build_icecandidate: function (cand) { - var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' '); - line += ' '; - switch (cand.type) { - case 'srflx': - case 'prflx': - case 'relay': - if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) { - line += 'raddr'; - line += ' '; - line += cand['rel-addr']; - line += ' '; - line += 'rport'; - line += ' '; - line += cand['rel-port']; - line += ' '; - } - break; - } - if (cand.hasOwnAttribute('tcptype')) { - line += 'tcptype'; - line += ' '; - line += cand.tcptype; - line += ' '; - } - line += 'generation'; - line += ' '; - line += cand.hasOwnAttribute('generation') ? cand.generation : '0'; - return line; - }, - parse_ssrc: function (desc) { - // proprietary mapping of a=ssrc lines - // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs - // and parse according to that - var lines = desc.split('\r\n'), - data = {}; - for (var i = 0; i < lines.length; i++) { - if (lines[i].substring(0, 7) == 'a=ssrc:') { - var idx = lines[i].indexOf(' '); - data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1]; - } - } - return data; - }, - parse_rtcpfb: function (line) { - var parts = line.substr(10).split(' '); - var data = {}; - data.pt = parts.shift(); - data.type = parts.shift(); - data.params = parts; - return data; - }, - parse_extmap: function (line) { - var parts = line.substr(9).split(' '); - var data = {}; - data.value = parts.shift(); - if (data.value.indexOf('/') != -1) { - data.direction = data.value.substr(data.value.indexOf('/') + 1); - data.value = data.value.substr(0, data.value.indexOf('/')); - } else { - data.direction = 'both'; - } - data.uri = parts.shift(); - data.params = parts; - return data; - }, - find_line: function (haystack, needle, sessionpart) { - var lines = haystack.split('\r\n'); - for (var i = 0; i < lines.length; i++) { - if (lines[i].substring(0, needle.length) == needle) { - return lines[i]; - } - } - if (!sessionpart) { - return false; - } - // search session part - lines = sessionpart.split('\r\n'); - for (var j = 0; j < lines.length; j++) { - if (lines[j].substring(0, needle.length) == needle) { - return lines[j]; - } - } - return false; - }, - find_lines: function (haystack, needle, sessionpart) { - var lines = haystack.split('\r\n'), - needles = []; - for (var i = 0; i < lines.length; i++) { - if (lines[i].substring(0, needle.length) == needle) - needles.push(lines[i]); - } - if (needles.length || !sessionpart) { - return needles; - } - // search session part - lines = sessionpart.split('\r\n'); - for (var j = 0; j < lines.length; j++) { - if (lines[j].substring(0, needle.length) == needle) { - needles.push(lines[j]); - } - } - return needles; - }, - candidateToJingle: function (line) { - // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0 - // - if (line.indexOf('candidate:') === 0) { - line = 'a=' + line; - } else if (line.substring(0, 12) != 'a=candidate:') { - console.log('parseCandidate called with a line that is not a candidate line'); - console.log(line); - return null; - } - if (line.substring(line.length - 2) == '\r\n') // chomp it - line = line.substring(0, line.length - 2); - var candidate = {}, - elems = line.split(' '), - i; - if (elems[6] != 'typ') { - console.log('did not find typ in the right place'); - console.log(line); - return null; - } - candidate.foundation = elems[0].substring(12); - candidate.component = elems[1]; - candidate.protocol = elems[2].toLowerCase(); - candidate.priority = elems[3]; - candidate.ip = elems[4]; - candidate.port = elems[5]; - // elems[6] => "typ" - candidate.type = elems[7]; - candidate.generation = '0'; // default, may be overwritten below - for (i = 8; i < elems.length; i += 2) { - switch (elems[i]) { - case 'raddr': - candidate['rel-addr'] = elems[i + 1]; - break; - case 'rport': - candidate['rel-port'] = elems[i + 1]; - break; - case 'generation': - candidate.generation = elems[i + 1]; - break; - case 'tcptype': - candidate.tcptype = elems[i + 1]; - break; - default: // TODO - console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"'); - } - } - candidate.network = '1'; - candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random - return candidate; - }, - candidateFromJingle: function (cand) { - var line = 'a=candidate:'; - line += cand.getAttribute('foundation'); - line += ' '; - line += cand.getAttribute('component'); - line += ' '; - line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this - line += ' '; - line += cand.getAttribute('priority'); - line += ' '; - line += cand.getAttribute('ip'); - line += ' '; - line += cand.getAttribute('port'); - line += ' '; - line += 'typ'; - line += ' ' + cand.getAttribute('type'); - line += ' '; - switch (cand.getAttribute('type')) { - case 'srflx': - case 'prflx': - case 'relay': - if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) { - line += 'raddr'; - line += ' '; - line += cand.getAttribute('rel-addr'); - line += ' '; - line += 'rport'; - line += ' '; - line += cand.getAttribute('rel-port'); - line += ' '; - } - break; - } - if (cand.getAttribute('protocol').toLowerCase() == 'tcp') { - line += 'tcptype'; - line += ' '; - line += cand.getAttribute('tcptype'); - line += ' '; - } - line += 'generation'; - line += ' '; - line += cand.getAttribute('generation') || '0'; - return line + '\r\n'; - } -}; +module.exports = SDP; diff --git a/modules/xmpp/SDPDiffer.js b/modules/xmpp/SDPDiffer.js new file mode 100644 index 000000000..ebaaadb13 --- /dev/null +++ b/modules/xmpp/SDPDiffer.js @@ -0,0 +1,165 @@ +function SDPDiffer(mySDP, otherSDP) { + this.mySDP = mySDP; + this.otherSDP = otherSDP; +} + +/** + * Returns map of MediaChannel that contains only media not contained in otherSdp. Mapped by channel idx. + * @param otherSdp the other SDP to check ssrc with. + */ +SDPDiffer.prototype.getNewMedia = function() { + + // this could be useful in Array.prototype. + function arrayEquals(array) { + // if the other array is a falsy value, return + if (!array) + return false; + + // compare lengths - can save a lot of time + if (this.length != array.length) + return false; + + for (var i = 0, l=this.length; i < l; i++) { + // Check if we have nested arrays + if (this[i] instanceof Array && array[i] instanceof Array) { + // recurse into the nested arrays + if (!this[i].equals(array[i])) + return false; + } + else if (this[i] != array[i]) { + // Warning - two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + return true; + } + + var myMedias = this.mySDP.getMediaSsrcMap(); + var othersMedias = this.otherSDP.getMediaSsrcMap(); + var newMedia = {}; + Object.keys(othersMedias).forEach(function(othersMediaIdx) { + var myMedia = myMedias[othersMediaIdx]; + var othersMedia = othersMedias[othersMediaIdx]; + if(!myMedia && othersMedia) { + // Add whole channel + newMedia[othersMediaIdx] = othersMedia; + return; + } + // Look for new ssrcs accross the channel + Object.keys(othersMedia.ssrcs).forEach(function(ssrc) { + if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) { + // Allocate channel if we've found ssrc that doesn't exist in our channel + if(!newMedia[othersMediaIdx]){ + newMedia[othersMediaIdx] = { + mediaindex: othersMedia.mediaindex, + mid: othersMedia.mid, + ssrcs: {}, + ssrcGroups: [] + }; + } + newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc]; + } + }); + + // Look for new ssrc groups across the channels + othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){ + + // try to match the other ssrc-group with an ssrc-group of ours + var matched = false; + for (var i = 0; i < myMedia.ssrcGroups.length; i++) { + var mySsrcGroup = myMedia.ssrcGroups[i]; + if (otherSsrcGroup.semantics == mySsrcGroup.semantics + && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) { + + matched = true; + break; + } + } + + if (!matched) { + // Allocate channel if we've found an ssrc-group that doesn't + // exist in our channel + + if(!newMedia[othersMediaIdx]){ + newMedia[othersMediaIdx] = { + mediaindex: othersMedia.mediaindex, + mid: othersMedia.mid, + ssrcs: {}, + ssrcGroups: [] + }; + } + newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup); + } + }); + }); + return newMedia; +}; + +/** + * Sends SSRC update IQ. + * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove. + * @param sid session identifier that will be put into the IQ. + * @param initiator initiator identifier. + * @param toJid destination Jid + * @param isAdd indicates if this is remove or add operation. + */ +SDPDiffer.prototype.toJingle = function(modify) { + var sdpMediaSsrcs = this.getNewMedia(); + var self = this; + + // FIXME: only announce video ssrcs since we mix audio and dont need + // the audio ssrcs therefore + var modified = false; + Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){ + modified = true; + var media = sdpMediaSsrcs[mediaindex]; + modify.c('content', {name: media.mid}); + + modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid}); + // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly + // generate sources from lines + Object.keys(media.ssrcs).forEach(function(ssrcNum) { + var mediaSsrc = media.ssrcs[ssrcNum]; + modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); + modify.attrs({ssrc: mediaSsrc.ssrc}); + // iterate over ssrc lines + mediaSsrc.lines.forEach(function (line) { + var idx = line.indexOf(' '); + var kv = line.substr(idx + 1); + modify.c('parameter'); + if (kv.indexOf(':') == -1) { + modify.attrs({ name: kv }); + } else { + modify.attrs({ name: kv.split(':', 2)[0] }); + modify.attrs({ value: kv.split(':', 2)[1] }); + } + modify.up(); // end of parameter + }); + modify.up(); // end of source + }); + + // generate source groups from lines + media.ssrcGroups.forEach(function(ssrcGroup) { + if (ssrcGroup.ssrcs.length != 0) { + + modify.c('ssrc-group', { + semantics: ssrcGroup.semantics, + xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' + }); + + ssrcGroup.ssrcs.forEach(function (ssrc) { + modify.c('source', { ssrc: ssrc }) + .up(); // end of source + }); + modify.up(); // end of ssrc-group + } + }); + + modify.up(); // end of description + modify.up(); // end of content + }); + + return modified; +}; + +module.exports = SDPDiffer; \ No newline at end of file diff --git a/modules/xmpp/SDPUtil.js b/modules/xmpp/SDPUtil.js new file mode 100644 index 000000000..d75d35f7a --- /dev/null +++ b/modules/xmpp/SDPUtil.js @@ -0,0 +1,349 @@ +SDPUtil = { + iceparams: function (mediadesc, sessiondesc) { + var data = null; + if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) && + SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) { + data = { + ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)), + pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) + }; + } + return data; + }, + parse_iceufrag: function (line) { + return line.substring(12); + }, + build_iceufrag: function (frag) { + return 'a=ice-ufrag:' + frag; + }, + parse_icepwd: function (line) { + return line.substring(10); + }, + build_icepwd: function (pwd) { + return 'a=ice-pwd:' + pwd; + }, + parse_mid: function (line) { + return line.substring(6); + }, + parse_mline: function (line) { + var parts = line.substring(2).split(' '), + data = {}; + data.media = parts.shift(); + data.port = parts.shift(); + data.proto = parts.shift(); + if (parts[parts.length - 1] === '') { // trailing whitespace + parts.pop(); + } + data.fmt = parts; + return data; + }, + build_mline: function (mline) { + return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' '); + }, + parse_rtpmap: function (line) { + var parts = line.substring(9).split(' '), + data = {}; + data.id = parts.shift(); + parts = parts[0].split('/'); + data.name = parts.shift(); + data.clockrate = parts.shift(); + data.channels = parts.length ? parts.shift() : '1'; + return data; + }, + /** + * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it. + * @param line eg. "a=sctpmap:5000 webrtc-datachannel" + * @returns [SCTP port number, protocol, streams] + */ + parse_sctpmap: function (line) + { + var parts = line.substring(10).split(' '); + var sctpPort = parts[0]; + var protocol = parts[1]; + // Stream count is optional + var streamCount = parts.length > 2 ? parts[2] : null; + return [sctpPort, protocol, streamCount];// SCTP port + }, + build_rtpmap: function (el) { + var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate'); + if (el.getAttribute('channels') && el.getAttribute('channels') != '1') { + line += '/' + el.getAttribute('channels'); + } + return line; + }, + parse_crypto: function (line) { + var parts = line.substring(9).split(' '), + data = {}; + data.tag = parts.shift(); + data['crypto-suite'] = parts.shift(); + data['key-params'] = parts.shift(); + if (parts.length) { + data['session-params'] = parts.join(' '); + } + return data; + }, + parse_fingerprint: function (line) { // RFC 4572 + var parts = line.substring(14).split(' '), + data = {}; + data.hash = parts.shift(); + data.fingerprint = parts.shift(); + // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ? + return data; + }, + parse_fmtp: function (line) { + var parts = line.split(' '), + i, key, value, + data = []; + parts.shift(); + parts = parts.join(' ').split(';'); + for (i = 0; i < parts.length; i++) { + key = parts[i].split('=')[0]; + while (key.length && key[0] == ' ') { + key = key.substring(1); + } + value = parts[i].split('=')[1]; + if (key && value) { + data.push({name: key, value: value}); + } else if (key) { + // rfc 4733 (DTMF) style stuff + data.push({name: '', value: key}); + } + } + return data; + }, + parse_icecandidate: function (line) { + var candidate = {}, + elems = line.split(' '); + candidate.foundation = elems[0].substring(12); + candidate.component = elems[1]; + candidate.protocol = elems[2].toLowerCase(); + candidate.priority = elems[3]; + candidate.ip = elems[4]; + candidate.port = elems[5]; + // elems[6] => "typ" + candidate.type = elems[7]; + candidate.generation = 0; // default value, may be overwritten below + for (var i = 8; i < elems.length; i += 2) { + switch (elems[i]) { + case 'raddr': + candidate['rel-addr'] = elems[i + 1]; + break; + case 'rport': + candidate['rel-port'] = elems[i + 1]; + break; + case 'generation': + candidate.generation = elems[i + 1]; + break; + case 'tcptype': + candidate.tcptype = elems[i + 1]; + break; + default: // TODO + console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"'); + } + } + candidate.network = '1'; + candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random + return candidate; + }, + build_icecandidate: function (cand) { + var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' '); + line += ' '; + switch (cand.type) { + case 'srflx': + case 'prflx': + case 'relay': + if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) { + line += 'raddr'; + line += ' '; + line += cand['rel-addr']; + line += ' '; + line += 'rport'; + line += ' '; + line += cand['rel-port']; + line += ' '; + } + break; + } + if (cand.hasOwnAttribute('tcptype')) { + line += 'tcptype'; + line += ' '; + line += cand.tcptype; + line += ' '; + } + line += 'generation'; + line += ' '; + line += cand.hasOwnAttribute('generation') ? cand.generation : '0'; + return line; + }, + parse_ssrc: function (desc) { + // proprietary mapping of a=ssrc lines + // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs + // and parse according to that + var lines = desc.split('\r\n'), + data = {}; + for (var i = 0; i < lines.length; i++) { + if (lines[i].substring(0, 7) == 'a=ssrc:') { + var idx = lines[i].indexOf(' '); + data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1]; + } + } + return data; + }, + parse_rtcpfb: function (line) { + var parts = line.substr(10).split(' '); + var data = {}; + data.pt = parts.shift(); + data.type = parts.shift(); + data.params = parts; + return data; + }, + parse_extmap: function (line) { + var parts = line.substr(9).split(' '); + var data = {}; + data.value = parts.shift(); + if (data.value.indexOf('/') != -1) { + data.direction = data.value.substr(data.value.indexOf('/') + 1); + data.value = data.value.substr(0, data.value.indexOf('/')); + } else { + data.direction = 'both'; + } + data.uri = parts.shift(); + data.params = parts; + return data; + }, + find_line: function (haystack, needle, sessionpart) { + var lines = haystack.split('\r\n'); + for (var i = 0; i < lines.length; i++) { + if (lines[i].substring(0, needle.length) == needle) { + return lines[i]; + } + } + if (!sessionpart) { + return false; + } + // search session part + lines = sessionpart.split('\r\n'); + for (var j = 0; j < lines.length; j++) { + if (lines[j].substring(0, needle.length) == needle) { + return lines[j]; + } + } + return false; + }, + find_lines: function (haystack, needle, sessionpart) { + var lines = haystack.split('\r\n'), + needles = []; + for (var i = 0; i < lines.length; i++) { + if (lines[i].substring(0, needle.length) == needle) + needles.push(lines[i]); + } + if (needles.length || !sessionpart) { + return needles; + } + // search session part + lines = sessionpart.split('\r\n'); + for (var j = 0; j < lines.length; j++) { + if (lines[j].substring(0, needle.length) == needle) { + needles.push(lines[j]); + } + } + return needles; + }, + candidateToJingle: function (line) { + // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0 + // + if (line.indexOf('candidate:') === 0) { + line = 'a=' + line; + } else if (line.substring(0, 12) != 'a=candidate:') { + console.log('parseCandidate called with a line that is not a candidate line'); + console.log(line); + return null; + } + if (line.substring(line.length - 2) == '\r\n') // chomp it + line = line.substring(0, line.length - 2); + var candidate = {}, + elems = line.split(' '), + i; + if (elems[6] != 'typ') { + console.log('did not find typ in the right place'); + console.log(line); + return null; + } + candidate.foundation = elems[0].substring(12); + candidate.component = elems[1]; + candidate.protocol = elems[2].toLowerCase(); + candidate.priority = elems[3]; + candidate.ip = elems[4]; + candidate.port = elems[5]; + // elems[6] => "typ" + candidate.type = elems[7]; + + candidate.generation = '0'; // default, may be overwritten below + for (i = 8; i < elems.length; i += 2) { + switch (elems[i]) { + case 'raddr': + candidate['rel-addr'] = elems[i + 1]; + break; + case 'rport': + candidate['rel-port'] = elems[i + 1]; + break; + case 'generation': + candidate.generation = elems[i + 1]; + break; + case 'tcptype': + candidate.tcptype = elems[i + 1]; + break; + default: // TODO + console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"'); + } + } + candidate.network = '1'; + candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random + return candidate; + }, + candidateFromJingle: function (cand) { + var line = 'a=candidate:'; + line += cand.getAttribute('foundation'); + line += ' '; + line += cand.getAttribute('component'); + line += ' '; + line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this + line += ' '; + line += cand.getAttribute('priority'); + line += ' '; + line += cand.getAttribute('ip'); + line += ' '; + line += cand.getAttribute('port'); + line += ' '; + line += 'typ'; + line += ' ' + cand.getAttribute('type'); + line += ' '; + switch (cand.getAttribute('type')) { + case 'srflx': + case 'prflx': + case 'relay': + if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) { + line += 'raddr'; + line += ' '; + line += cand.getAttribute('rel-addr'); + line += ' '; + line += 'rport'; + line += ' '; + line += cand.getAttribute('rel-port'); + line += ' '; + } + break; + } + if (cand.getAttribute('protocol').toLowerCase() == 'tcp') { + line += 'tcptype'; + line += ' '; + line += cand.getAttribute('tcptype'); + line += ' '; + } + line += 'generation'; + line += ' '; + line += cand.getAttribute('generation') || '0'; + return line + '\r\n'; + } +}; +module.exports = SDPUtil; \ No newline at end of file diff --git a/libs/strophe/strophe.jingle.adapter.js b/modules/xmpp/TraceablePeerConnection.js similarity index 99% rename from libs/strophe/strophe.jingle.adapter.js rename to modules/xmpp/TraceablePeerConnection.js index 035c43ab3..c8db15337 100644 --- a/libs/strophe/strophe.jingle.adapter.js +++ b/modules/xmpp/TraceablePeerConnection.js @@ -262,3 +262,5 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) { } }; +module.exports = TraceablePeerConnection; + diff --git a/moderator.js b/modules/xmpp/moderator.js similarity index 70% rename from moderator.js rename to modules/xmpp/moderator.js index 5f0ed3bd8..439d70311 100644 --- a/moderator.js +++ b/modules/xmpp/moderator.js @@ -1,47 +1,53 @@ -/* global $, $iq, config, connection, Etherpad, hangUp, messageHandler, +/* global $, $iq, config, connection, UI, messageHandler, roomName, sessionTerminated, Strophe, Util */ /** * Contains logic responsible for enabling/disabling functionality available * only to moderator users. */ -var Moderator = (function (my) { +var connection = null; +var focusUserJid; +var getNextTimeout = Util.createExpBackoffTimer(1000); +var getNextErrorTimeout = Util.createExpBackoffTimer(1000); +// External authentication stuff +var externalAuthEnabled = false; +// Sip gateway can be enabled by configuring Jigasi host in config.js or +// it will be enabled automatically if focus detects the component through +// service discovery. +var sipGatewayEnabled = config.hosts.call_control !== undefined; - var focusUserJid; - var getNextTimeout = Util.createExpBackoffTimer(1000); - var getNextErrorTimeout = Util.createExpBackoffTimer(1000); - // External authentication stuff - var externalAuthEnabled = false; - // Sip gateway can be enabled by configuring Jigasi host in config.js or - // it will be enabled automatically if focus detects the component through - // service discovery. - var sipGatewayEnabled = config.hosts.call_control !== undefined; - - my.isModerator = function () { +var Moderator = { + isModerator: function () { return connection && connection.emuc.isModerator(); - }; + }, - my.isPeerModerator = function (peerJid) { - return connection && connection.emuc.getMemberRole(peerJid) === 'moderator'; - }; + isPeerModerator: function (peerJid) { + return connection && + connection.emuc.getMemberRole(peerJid) === 'moderator'; + }, - my.isExternalAuthEnabled = function () { + isExternalAuthEnabled: function () { return externalAuthEnabled; - }; + }, - my.isSipGatewayEnabled = function () { + isSipGatewayEnabled: function () { return sipGatewayEnabled; - }; + }, - my.init = function () { - Moderator.onLocalRoleChange = function (from, member, pres) { + setConnection: function (con) { + connection = con; + }, + + init: function (xmpp) { + this.xmppService = xmpp; + this.onLocalRoleChange = function (from, member, pres) { UI.onModeratorStatusChanged(Moderator.isModerator()); }; - }; + }, - my.onMucLeft = function (jid) { + onMucLeft: function (jid) { console.info("Someone left is it focus ? " + jid); var resource = Strophe.getResourceFromJid(jid); - if (resource === 'focus' && !sessionTerminated) { + if (resource === 'focus' && !this.xmppService.sessionTerminated) { console.info( "Focus has left the room - leaving conference"); //hangUp(); @@ -49,20 +55,20 @@ var Moderator = (function (my) { // FIXME: show some message before reload location.reload(); } - } - - my.setFocusUserJid = function (focusJid) { + }, + + setFocusUserJid: function (focusJid) { if (!focusUserJid) { focusUserJid = focusJid; console.info("Focus jid set to: " + focusUserJid); } - }; + }, - my.getFocusUserJid = function () { + getFocusUserJid: function () { return focusUserJid; - }; + }, - my.getFocusComponent = function () { + getFocusComponent: function () { // Get focus component address var focusComponent = config.hosts.focus; // If not specified use default: 'focus.domain' @@ -70,99 +76,93 @@ var Moderator = (function (my) { focusComponent = 'focus.' + config.hosts.domain; } return focusComponent; - }; + }, - my.createConferenceIq = function () { + createConferenceIq: function (roomName) { // Generate create conference IQ var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'}); elem.c('conference', { xmlns: 'http://jitsi.org/protocol/focus', room: roomName }); - if (config.hosts.bridge !== undefined) - { + if (config.hosts.bridge !== undefined) { elem.c( 'property', { name: 'bridge', value: config.hosts.bridge}) .up(); } // Tell the focus we have Jigasi configured - if (config.hosts.call_control !== undefined) - { + if (config.hosts.call_control !== undefined) { elem.c( 'property', { name: 'call_control', value: config.hosts.call_control}) .up(); } - if (config.channelLastN !== undefined) - { + if (config.channelLastN !== undefined) { elem.c( 'property', { name: 'channelLastN', value: config.channelLastN}) .up(); } - if (config.adaptiveLastN !== undefined) - { + if (config.adaptiveLastN !== undefined) { elem.c( 'property', { name: 'adaptiveLastN', value: config.adaptiveLastN}) .up(); } - if (config.adaptiveSimulcast !== undefined) - { + if (config.adaptiveSimulcast !== undefined) { elem.c( 'property', { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast}) .up(); } - if (config.openSctp !== undefined) - { + if (config.openSctp !== undefined) { elem.c( 'property', { name: 'openSctp', value: config.openSctp}) .up(); } - if (config.enableFirefoxSupport !== undefined) - { + if (config.enableFirefoxSupport !== undefined) { elem.c( 'property', - { name: 'enableFirefoxHacks', value: config.enableFirefoxSupport}) + { name: 'enableFirefoxHacks', + value: config.enableFirefoxSupport}) .up(); } elem.up(); return elem; - }; - - my.parseConfigOptions = function (resultIq) { + }, + parseConfigOptions: function (resultIq) { + Moderator.setFocusUserJid( $(resultIq).find('conference').attr('focusjid')); - + var extAuthParam = $(resultIq).find('>conference>property[name=\'externalAuth\']'); if (extAuthParam.length) { externalAuthEnabled = extAuthParam.attr('value') === 'true'; } - + console.info("External authentication enabled: " + externalAuthEnabled); - + // Check if focus has auto-detected Jigasi component(this will be also // included if we have passed our host from the config) if ($(resultIq).find( - '>conference>property[name=\'sipGatewayEnabled\']').length) { + '>conference>property[name=\'sipGatewayEnabled\']').length) { sipGatewayEnabled = true; } - + console.info("Sip gateway enabled: " + sipGatewayEnabled); - }; + }, // FIXME: we need to show the fact that we're waiting for the focus // to the user(or that focus is not available) - my.allocateConferenceFocus = function (roomName, callback) { + allocateConferenceFocus: function (roomName, callback) { // Try to use focus user JID from the config Moderator.setFocusUserJid(config.focusUserJid); // Send create conference IQ - var iq = Moderator.createConferenceIq(); + var iq = Moderator.createConferenceIq(roomName); connection.sendIQ( iq, function (result) { @@ -190,7 +190,9 @@ var Moderator = (function (my) { // Not authorized to create new room if ($(error).find('>error>not-authorized').length) { console.warn("Unauthorized to start the conference"); - UI.onAuthenticationRequired(); + UI.onAuthenticationRequired(function () { + Moderator.allocateConferenceFocus(roomName, callback); + }); return; } var waitMs = getNextErrorTimeout(); @@ -198,8 +200,9 @@ var Moderator = (function (my) { // Show message UI.messageHandler.notify( 'Conference focus', 'disconnected', - Moderator.getFocusComponent() + - ' not available - retry in ' + (waitMs / 1000) + ' sec'); + Moderator.getFocusComponent() + + ' not available - retry in ' + + (waitMs / 1000) + ' sec'); // Reset response timeout getNextTimeout(true); window.setTimeout( @@ -208,9 +211,9 @@ var Moderator = (function (my) { }, waitMs); } ); - }; + }, - my.getAuthUrl = function (urlCallback) { + getAuthUrl: function (roomName, urlCallback) { var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'}); iq.c('auth-url', { xmlns: 'http://jitsi.org/protocol/focus', @@ -232,10 +235,10 @@ var Moderator = (function (my) { console.error("Get auth url error", error); } ); - }; + } +}; - return my; -}(Moderator || {})); +module.exports = Moderator; diff --git a/modules/xmpp/recording.js b/modules/xmpp/recording.js new file mode 100644 index 000000000..245260c49 --- /dev/null +++ b/modules/xmpp/recording.js @@ -0,0 +1,152 @@ +/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator, + Toolbar, Util */ +var Moderator = require("./moderator"); + + +var recordingToken = null; +var recordingEnabled; + +/** + * Whether to use a jirecon component for recording, or use the videobridge + * through COLIBRI. + */ +var useJirecon = (typeof config.hosts.jirecon != "undefined"); + +/** + * The ID of the jirecon recording session. Jirecon generates it when we + * initially start recording, and it needs to be used in subsequent requests + * to jirecon. + */ +var jireconRid = null; + +function setRecordingToken(token) { + recordingToken = token; +} + +function setRecording(state, token, callback) { + if (useJirecon){ + this.setRecordingJirecon(state, token, callback); + } else { + this.setRecordingColibri(state, token, callback); + } +} + +function setRecordingJirecon(state, token, callback) { + if (state == recordingEnabled){ + return; + } + + var iq = $iq({to: config.hosts.jirecon, type: 'set'}) + .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon', + action: state ? 'start' : 'stop', + mucjid: connection.emuc.roomjid}); + if (!state){ + iq.attrs({rid: jireconRid}); + } + + console.log('Start recording'); + + connection.sendIQ( + iq, + function (result) { + // TODO wait for an IQ with the real status, since this is + // provisional? + jireconRid = $(result).find('recording').attr('rid'); + console.log('Recording ' + (state ? 'started' : 'stopped') + + '(jirecon)' + result); + recordingEnabled = state; + if (!state){ + jireconRid = null; + } + + callback(state); + }, + function (error) { + console.log('Failed to start recording, error: ', error); + callback(recordingEnabled); + }); +} + +// Sends a COLIBRI message which enables or disables (according to 'state') +// the recording on the bridge. Waits for the result IQ and calls 'callback' +// with the new recording state, according to the IQ. +function setRecordingColibri(state, token, callback) { + var elem = $iq({to: focusMucJid, type: 'set'}); + elem.c('conference', { + xmlns: 'http://jitsi.org/protocol/colibri' + }); + elem.c('recording', {state: state, token: token}); + + connection.sendIQ(elem, + function (result) { + console.log('Set recording "', state, '". Result:', result); + var recordingElem = $(result).find('>conference>recording'); + var newState = ('true' === recordingElem.attr('state')); + + recordingEnabled = newState; + callback(newState); + }, + function (error) { + console.warn(error); + callback(recordingEnabled); + } + ); +} + +var Recording = { + toggleRecording: function (tokenEmptyCallback, + startingCallback, startedCallback) { + if (!Moderator.isModerator()) { + console.log( + 'non-focus, or conference not yet organized:' + + ' not enabling recording'); + return; + } + + // Jirecon does not (currently) support a token. + if (!recordingToken && !useJirecon) { + tokenEmptyCallback(function (value) { + setRecordingToken(value); + this.toggleRecording(); + }); + + return; + } + + var oldState = recordingEnabled; + startingCallback(!oldState); + setRecording(!oldState, + recordingToken, + function (state) { + console.log("New recording state: ", state); + if (state === oldState) { + // FIXME: new focus: + // this will not work when moderator changes + // during active session. Then it will assume that + // recording status has changed to true, but it might have + // been already true(and we only received actual status from + // the focus). + // + // SO we start with status null, so that it is initialized + // here and will fail only after second click, so if invalid + // token was used we have to press the button twice before + // current status will be fetched and token will be reset. + // + // Reliable way would be to return authentication error. + // Or status update when moderator connects. + // Or we have to stop recording session when current + // moderator leaves the room. + + // Failed to change, reset the token because it might + // have been wrong + setRecordingToken(null); + } + startedCallback(state); + + } + ); + } + +} + +module.exports = Recording; \ No newline at end of file diff --git a/modules/xmpp/strophe.emuc.js b/modules/xmpp/strophe.emuc.js new file mode 100644 index 000000000..63bf14713 --- /dev/null +++ b/modules/xmpp/strophe.emuc.js @@ -0,0 +1,607 @@ +/* jshint -W117 */ +/* a simple MUC connection plugin + * can only handle a single MUC room + */ + +var bridgeIsDown = false; + +var Moderator = require("./moderator"); + +module.exports = function(XMPP, eventEmitter) { + Strophe.addConnectionPlugin('emuc', { + connection: null, + roomjid: null, + myroomjid: null, + members: {}, + list_members: [], // so we can elect a new focus + presMap: {}, + preziMap: {}, + joined: false, + isOwner: false, + role: null, + init: function (conn) { + this.connection = conn; + }, + initPresenceMap: function (myroomjid) { + this.presMap['to'] = myroomjid; + this.presMap['xns'] = 'http://jabber.org/protocol/muc'; + }, + doJoin: function (jid, password) { + this.myroomjid = jid; + + console.info("Joined MUC as " + this.myroomjid); + + this.initPresenceMap(this.myroomjid); + + if (!this.roomjid) { + this.roomjid = Strophe.getBareJidFromJid(jid); + // add handlers (just once) + this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true}); + this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true}); + this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true}); + this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true}); + } + if (password !== undefined) { + this.presMap['password'] = password; + } + this.sendPresence(); + }, + doLeave: function () { + console.log("do leave", this.myroomjid); + var pres = $pres({to: this.myroomjid, type: 'unavailable' }); + this.presMap.length = 0; + this.connection.send(pres); + }, + createNonAnonymousRoom: function () { + // http://xmpp.org/extensions/xep-0045.html#createroom-reserved + + var getForm = $iq({type: 'get', to: this.roomjid}) + .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}) + .c('x', {xmlns: 'jabber:x:data', type: 'submit'}); + + this.connection.sendIQ(getForm, function (form) { + + if (!$(form).find( + '>query>x[xmlns="jabber:x:data"]' + + '>field[var="muc#roomconfig_whois"]').length) { + + console.error('non-anonymous rooms not supported'); + return; + } + + var formSubmit = $iq({to: this.roomjid, type: 'set'}) + .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}); + + formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'}); + + formSubmit.c('field', {'var': 'FORM_TYPE'}) + .c('value') + .t('http://jabber.org/protocol/muc#roomconfig').up().up(); + + formSubmit.c('field', {'var': 'muc#roomconfig_whois'}) + .c('value').t('anyone').up().up(); + + this.connection.sendIQ(formSubmit); + + }, function (error) { + console.error("Error getting room configuration form"); + }); + }, + onPresence: function (pres) { + var from = pres.getAttribute('from'); + + // What is this for? A workaround for something? + if (pres.getAttribute('type')) { + return true; + } + + // Parse etherpad tag. + var etherpad = $(pres).find('>etherpad'); + if (etherpad.length) { + if (config.etherpad_base && !Moderator.isModerator()) { + UI.initEtherpad(etherpad.text()); + } + } + + // Parse prezi tag. + var presentation = $(pres).find('>prezi'); + if (presentation.length) { + var url = presentation.attr('url'); + var current = presentation.find('>current').text(); + + console.log('presentation info received from', from, url); + + if (this.preziMap[from] == null) { + this.preziMap[from] = url; + + $(document).trigger('presentationadded.muc', [from, url, current]); + } + else { + $(document).trigger('gotoslide.muc', [from, url, current]); + } + } + else if (this.preziMap[from] != null) { + var url = this.preziMap[from]; + delete this.preziMap[from]; + $(document).trigger('presentationremoved.muc', [from, url]); + } + + // Parse audio info tag. + var audioMuted = $(pres).find('>audiomuted'); + if (audioMuted.length) { + $(document).trigger('audiomuted.muc', [from, audioMuted.text()]); + } + + // Parse video info tag. + var videoMuted = $(pres).find('>videomuted'); + if (videoMuted.length) { + $(document).trigger('videomuted.muc', [from, videoMuted.text()]); + } + + var stats = $(pres).find('>stats'); + if (stats.length) { + var statsObj = {}; + Strophe.forEachChild(stats[0], "stat", function (el) { + statsObj[el.getAttribute("name")] = el.getAttribute("value"); + }); + connectionquality.updateRemoteStats(from, statsObj); + } + + // Parse status. + if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) { + this.isOwner = true; + this.createNonAnonymousRoom(); + } + + // Parse roles. + var member = {}; + member.show = $(pres).find('>show').text(); + member.status = $(pres).find('>status').text(); + var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item'); + member.affiliation = tmp.attr('affiliation'); + member.role = tmp.attr('role'); + + // Focus recognition + member.jid = tmp.attr('jid'); + member.isFocus = false; + if (member.jid + && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) { + member.isFocus = true; + } + + var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]'); + member.displayName = (nicktag.length > 0 ? nicktag.html() : null); + + if (from == this.myroomjid) { + if (member.affiliation == 'owner') this.isOwner = true; + if (this.role !== member.role) { + this.role = member.role; + if (Moderator.onLocalRoleChange) + Moderator.onLocalRoleChange(from, member, pres); + UI.onLocalRoleChange(from, member, pres); + } + if (!this.joined) { + this.joined = true; + eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member); + this.list_members.push(from); + } + } else if (this.members[from] === undefined) { + // new participant + this.members[from] = member; + this.list_members.push(from); + console.log('entered', from, member); + if (member.isFocus) { + focusMucJid = from; + console.info("Ignore focus: " + from + ", real JID: " + member.jid); + } + else { + var id = $(pres).find('>userID').text(); + var email = $(pres).find('>email'); + if (email.length > 0) { + id = email.text(); + } + UI.onMucEntered(from, id, member.displayName); + API.triggerEvent("participantJoined", {jid: from}); + } + } else { + // Presence update for existing participant + // Watch role change: + if (this.members[from].role != member.role) { + this.members[from].role = member.role; + UI.onMucRoleChanged(member.role, member.displayName); + } + } + + // Always trigger presence to update bindings + $(document).trigger('presence.muc', [from, member, pres]); + this.parsePresence(from, member, pres); + + // Trigger status message update + if (member.status) { + UI.onMucPresenceStatus(from, member); + } + + return true; + }, + onPresenceUnavailable: function (pres) { + var from = pres.getAttribute('from'); + // Status code 110 indicates that this notification is "self-presence". + if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) { + delete this.members[from]; + this.list_members.splice(this.list_members.indexOf(from), 1); + this.onParticipantLeft(from); + } + // If the status code is 110 this means we're leaving and we would like + // to remove everyone else from our view, so we trigger the event. + else if (this.list_members.length > 1) { + for (var i = 0; i < this.list_members.length; i++) { + var member = this.list_members[i]; + delete this.members[i]; + this.list_members.splice(i, 1); + this.onParticipantLeft(member); + } + } + if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) { + $(document).trigger('kicked.muc', [from]); + if (this.myroomjid === from) { + XMPP.disposeConference(false); + eventEmitter.emit(XMPPEvents.KICKED); + } + } + return true; + }, + onPresenceError: function (pres) { + var from = pres.getAttribute('from'); + if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { + console.log('on password required', from); + var self = this; + UI.onPasswordReqiured(function (value) { + self.doJoin(from, value); + }); + } else if ($(pres).find( + '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { + var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to')); + if (toDomain === config.hosts.anonymousdomain) { + // we are connected with anonymous domain and only non anonymous users can create rooms + // we must authorize the user + XMPP.promptLogin(); + } else { + console.warn('onPresError ', pres); + UI.messageHandler.openReportDialog(null, + 'Oops! Something went wrong and we couldn`t connect to the conference.', + pres); + } + } else { + console.warn('onPresError ', pres); + UI.messageHandler.openReportDialog(null, + 'Oops! Something went wrong and we couldn`t connect to the conference.', + pres); + } + return true; + }, + sendMessage: function (body, nickname) { + var msg = $msg({to: this.roomjid, type: 'groupchat'}); + msg.c('body', body).up(); + if (nickname) { + msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up(); + } + this.connection.send(msg); + API.triggerEvent("outgoingMessage", {"message": body}); + }, + setSubject: function (subject) { + var msg = $msg({to: this.roomjid, type: 'groupchat'}); + msg.c('subject', subject); + this.connection.send(msg); + console.log("topic changed to " + subject); + }, + onMessage: function (msg) { + // FIXME: this is a hack. but jingle on muc makes nickchanges hard + var from = msg.getAttribute('from'); + var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from); + + var txt = $(msg).find('>body').text(); + var type = msg.getAttribute("type"); + if (type == "error") { + UI.chatAddError($(msg).find('>text').text(), txt); + return true; + } + + var subject = $(msg).find('>subject'); + if (subject.length) { + var subjectText = subject.text(); + if (subjectText || subjectText == "") { + UI.chatSetSubject(subjectText); + console.log("Subject is changed to " + subjectText); + } + } + + + if (txt) { + console.log('chat', nick, txt); + UI.updateChatConversation(from, nick, txt); + if (from != this.myroomjid) + API.triggerEvent("incomingMessage", + {"from": from, "nick": nick, "message": txt}); + } + return true; + }, + lockRoom: function (key, onSuccess, onError, onNotSupported) { + //http://xmpp.org/extensions/xep-0045.html#roomconfig + var ob = this; + this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}), + function (res) { + if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) { + var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}); + formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'}); + formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up(); + formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up(); + // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373 + formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up(); + // FIXME: is muc#roomconfig_passwordprotectedroom required? + this.connection.sendIQ(formsubmit, + onSuccess, + onError); + } else { + onNotSupported(); + } + }, onError); + }, + kick: function (jid) { + var kickIQ = $iq({to: this.roomjid, type: 'set'}) + .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'}) + .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'}) + .c('reason').t('You have been kicked.').up().up().up(); + + this.connection.sendIQ( + kickIQ, + function (result) { + console.log('Kick participant with jid: ', jid, result); + }, + function (error) { + console.log('Kick participant error: ', error); + }); + }, + sendPresence: function () { + var pres = $pres({to: this.presMap['to'] }); + pres.c('x', {xmlns: this.presMap['xns']}); + + if (this.presMap['password']) { + pres.c('password').t(this.presMap['password']).up(); + } + + pres.up(); + + // Send XEP-0115 'c' stanza that contains our capabilities info + if (this.connection.caps) { + this.connection.caps.node = config.clientNode; + pres.c('c', this.connection.caps.generateCapsAttrs()).up(); + } + + pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'}) + .t(navigator.userAgent).up(); + + if (this.presMap['bridgeIsDown']) { + pres.c('bridgeIsDown').up(); + } + + if (this.presMap['email']) { + pres.c('email').t(this.presMap['email']).up(); + } + + if (this.presMap['userId']) { + pres.c('userId').t(this.presMap['userId']).up(); + } + + if (this.presMap['displayName']) { + // XEP-0172 + pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}) + .t(this.presMap['displayName']).up(); + } + + if (this.presMap['audions']) { + pres.c('audiomuted', {xmlns: this.presMap['audions']}) + .t(this.presMap['audiomuted']).up(); + } + + if (this.presMap['videons']) { + pres.c('videomuted', {xmlns: this.presMap['videons']}) + .t(this.presMap['videomuted']).up(); + } + + if (this.presMap['statsns']) { + var stats = pres.c('stats', {xmlns: this.presMap['statsns']}); + for (var stat in this.presMap["stats"]) + if (this.presMap["stats"][stat] != null) + stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up(); + pres.up(); + } + + if (this.presMap['prezins']) { + pres.c('prezi', + {xmlns: this.presMap['prezins'], + 'url': this.presMap['preziurl']}) + .c('current').t(this.presMap['prezicurrent']).up().up(); + } + + if (this.presMap['etherpadns']) { + pres.c('etherpad', {xmlns: this.presMap['etherpadns']}) + .t(this.presMap['etherpadname']).up(); + } + + if (this.presMap['medians']) { + pres.c('media', {xmlns: this.presMap['medians']}); + var sourceNumber = 0; + Object.keys(this.presMap).forEach(function (key) { + if (key.indexOf('source') >= 0) { + sourceNumber++; + } + }); + if (sourceNumber > 0) + for (var i = 1; i <= sourceNumber / 3; i++) { + pres.c('source', + {type: this.presMap['source' + i + '_type'], + ssrc: this.presMap['source' + i + '_ssrc'], + direction: this.presMap['source' + i + '_direction'] + || 'sendrecv' } + ).up(); + } + } + + pres.up(); +// console.debug(pres.toString()); + this.connection.send(pres); + }, + addDisplayNameToPresence: function (displayName) { + this.presMap['displayName'] = displayName; + }, + addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) { + if (!this.presMap['medians']) + this.presMap['medians'] = 'http://estos.de/ns/mjs'; + + this.presMap['source' + sourceNumber + '_type'] = mtype; + this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs; + this.presMap['source' + sourceNumber + '_direction'] = direction; + }, + clearPresenceMedia: function () { + var self = this; + Object.keys(this.presMap).forEach(function (key) { + if (key.indexOf('source') != -1) { + delete self.presMap[key]; + } + }); + }, + addPreziToPresence: function (url, currentSlide) { + this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi'; + this.presMap['preziurl'] = url; + this.presMap['prezicurrent'] = currentSlide; + }, + removePreziFromPresence: function () { + delete this.presMap['prezins']; + delete this.presMap['preziurl']; + delete this.presMap['prezicurrent']; + }, + addCurrentSlideToPresence: function (currentSlide) { + this.presMap['prezicurrent'] = currentSlide; + }, + getPrezi: function (roomjid) { + return this.preziMap[roomjid]; + }, + addEtherpadToPresence: function (etherpadName) { + this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad'; + this.presMap['etherpadname'] = etherpadName; + }, + addAudioInfoToPresence: function (isMuted) { + this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio'; + this.presMap['audiomuted'] = isMuted.toString(); + }, + addVideoInfoToPresence: function (isMuted) { + this.presMap['videons'] = 'http://jitsi.org/jitmeet/video'; + this.presMap['videomuted'] = isMuted.toString(); + }, + addConnectionInfoToPresence: function (stats) { + this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats'; + this.presMap['stats'] = stats; + }, + findJidFromResource: function (resourceJid) { + if (resourceJid && + resourceJid === Strophe.getResourceFromJid(this.myroomjid)) { + return this.myroomjid; + } + var peerJid = null; + Object.keys(this.members).some(function (jid) { + peerJid = jid; + return Strophe.getResourceFromJid(jid) === resourceJid; + }); + return peerJid; + }, + addBridgeIsDownToPresence: function () { + this.presMap['bridgeIsDown'] = true; + }, + addEmailToPresence: function (email) { + this.presMap['email'] = email; + }, + addUserIdToPresence: function (userId) { + this.presMap['userId'] = userId; + }, + isModerator: function () { + return this.role === 'moderator'; + }, + getMemberRole: function (peerJid) { + if (this.members[peerJid]) { + return this.members[peerJid].role; + } + return null; + }, + onParticipantLeft: function (jid) { + UI.onMucLeft(jid); + + API.triggerEvent("participantLeft", {jid: jid}); + + delete jid2Ssrc[jid]; + + this.connection.jingle.terminateByJid(jid); + + if (this.getPrezi(jid)) { + $(document).trigger('presentationremoved.muc', + [jid, this.getPrezi(jid)]); + } + + Moderator.onMucLeft(jid); + }, + parsePresence: function (from, memeber, pres) { + if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) { + bridgeIsDown = true; + eventEmitter.emit(XMPPEvents.BRIDGE_DOWN); + } + + if(memeber.isFocus) + return; + + // Remove old ssrcs coming from the jid + Object.keys(ssrc2jid).forEach(function (ssrc) { + if (ssrc2jid[ssrc] == jid) { + delete ssrc2jid[ssrc]; + delete ssrc2videoType[ssrc]; + } + }); + + var changedStreams = []; + $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) { + //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc')); + var ssrcV = ssrc.getAttribute('ssrc'); + ssrc2jid[ssrcV] = from; + notReceivedSSRCs.push(ssrcV); + + var type = ssrc.getAttribute('type'); + ssrc2videoType[ssrcV] = type; + + var direction = ssrc.getAttribute('direction'); + + changedStreams.push({type: type, direction: direction}); + + }); + + eventEmitter.emit(XMPPEvents.CHANGED_STREAMS, from, changedStreams); + + var displayName = !config.displayJids + ? memeber.displayName : Strophe.getResourceFromJid(from); + + if (displayName && displayName.length > 0) + { +// $(document).trigger('displaynamechanged', +// [jid, displayName]); + eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName); + } + + + var id = $(pres).find('>userID').text(); + var email = $(pres).find('>email'); + if(email.length > 0) { + id = email.text(); + } + + eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id); + } + }); +}; + diff --git a/modules/xmpp/strophe.jingle.js b/modules/xmpp/strophe.jingle.js new file mode 100644 index 000000000..4878d587c --- /dev/null +++ b/modules/xmpp/strophe.jingle.js @@ -0,0 +1,334 @@ +/* jshint -W117 */ + +var JingleSession = require("./JingleSession"); + +function CallIncomingJingle(sid, connection) { + var sess = connection.jingle.sessions[sid]; + + // TODO: do we check activecall == null? + activecall = sess; + + statistics.onConferenceCreated(sess); + RTC.onConferenceCreated(sess); + + // TODO: check affiliation and/or role + console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]); + sess.usedrip = true; // not-so-naive trickle ice + sess.sendAnswer(); + sess.accept(); + +}; + +module.exports = function(XMPP) +{ + Strophe.addConnectionPlugin('jingle', { + connection: null, + sessions: {}, + jid2session: {}, + ice_config: {iceServers: []}, + pc_constraints: {}, + media_constraints: { + mandatory: { + 'OfferToReceiveAudio': true, + 'OfferToReceiveVideo': true + } + // MozDontOfferDataChannel: true when this is firefox + }, + init: function (conn) { + this.connection = conn; + if (this.connection.disco) { + // http://xmpp.org/extensions/xep-0167.html#support + // http://xmpp.org/extensions/xep-0176.html#support + this.connection.disco.addFeature('urn:xmpp:jingle:1'); + this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1'); + this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1'); + this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio'); + this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video'); + + + // this is dealt with by SDP O/A so we don't need to annouce this + //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293 + //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294 + if (config.useRtcpMux) { + this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux + } + if (config.useBundle) { + this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle + } + //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc + } + this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null); + }, + onJingle: function (iq) { + var sid = $(iq).find('jingle').attr('sid'); + var action = $(iq).find('jingle').attr('action'); + var fromJid = iq.getAttribute('from'); + // send ack first + var ack = $iq({type: 'result', + to: fromJid, + id: iq.getAttribute('id') + }); + console.log('on jingle ' + action + ' from ' + fromJid, iq); + var sess = this.sessions[sid]; + if ('session-initiate' != action) { + if (sess === null) { + ack.type = 'error'; + ack.c('error', {type: 'cancel'}) + .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up() + .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'}); + this.connection.send(ack); + return true; + } + // compare from to sess.peerjid (bare jid comparison for later compat with message-mode) + // local jid is not checked + if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) { + console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid); + ack.type = 'error'; + ack.c('error', {type: 'cancel'}) + .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up() + .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'}); + this.connection.send(ack); + return true; + } + } else if (sess !== undefined) { + // existing session with same session id + // this might be out-of-order if the sess.peerjid is the same as from + ack.type = 'error'; + ack.c('error', {type: 'cancel'}) + .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up(); + console.warn('duplicate session id', sid); + this.connection.send(ack); + return true; + } + // FIXME: check for a defined action + this.connection.send(ack); + // see http://xmpp.org/extensions/xep-0166.html#concepts-session + switch (action) { + case 'session-initiate': + sess = new JingleSession( + $(iq).attr('to'), $(iq).find('jingle').attr('sid'), + this.connection, XMPP); + // configure session + + sess.media_constraints = this.media_constraints; + sess.pc_constraints = this.pc_constraints; + sess.ice_config = this.ice_config; + + sess.initiate(fromJid, false); + // FIXME: setRemoteDescription should only be done when this call is to be accepted + sess.setRemoteDescription($(iq).find('>jingle'), 'offer'); + + this.sessions[sess.sid] = sess; + this.jid2session[sess.peerjid] = sess; + + // the callback should either + // .sendAnswer and .accept + // or .sendTerminate -- not necessarily synchronus + CallIncomingJingle(sess.sid, this.connection); + break; + case 'session-accept': + sess.setRemoteDescription($(iq).find('>jingle'), 'answer'); + sess.accept(); + $(document).trigger('callaccepted.jingle', [sess.sid]); + break; + case 'session-terminate': + // If this is not the focus sending the terminate, we have + // nothing more to do here. + if (Object.keys(this.sessions).length < 1 + || !(this.sessions[Object.keys(this.sessions)[0]] + instanceof JingleSession)) + { + break; + } + console.log('terminating...', sess.sid); + sess.terminate(); + this.terminate(sess.sid); + if ($(iq).find('>jingle>reason').length) { + $(document).trigger('callterminated.jingle', [ + sess.sid, + sess.peerjid, + $(iq).find('>jingle>reason>:first')[0].tagName, + $(iq).find('>jingle>reason>text').text() + ]); + } else { + $(document).trigger('callterminated.jingle', + [sess.sid, sess.peerjid]); + } + break; + case 'transport-info': + sess.addIceCandidate($(iq).find('>jingle>content')); + break; + case 'session-info': + var affected; + if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { + $(document).trigger('ringing.jingle', [sess.sid]); + } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { + affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name'); + $(document).trigger('mute.jingle', [sess.sid, affected]); + } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) { + affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name'); + $(document).trigger('unmute.jingle', [sess.sid, affected]); + } + break; + case 'addsource': // FIXME: proprietary, un-jingleish + case 'source-add': // FIXME: proprietary + sess.addSource($(iq).find('>jingle>content'), fromJid); + break; + case 'removesource': // FIXME: proprietary, un-jingleish + case 'source-remove': // FIXME: proprietary + sess.removeSource($(iq).find('>jingle>content'), fromJid); + break; + default: + console.warn('jingle action not implemented', action); + break; + } + return true; + }, + initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid + var sess = new JingleSession(myjid || this.connection.jid, + Math.random().toString(36).substr(2, 12), // random string + this.connection, XMPP); + // configure session + + sess.media_constraints = this.media_constraints; + sess.pc_constraints = this.pc_constraints; + sess.ice_config = this.ice_config; + + sess.initiate(peerjid, true); + this.sessions[sess.sid] = sess; + this.jid2session[sess.peerjid] = sess; + sess.sendOffer(); + return sess; + }, + terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions) + if (sid === null || sid === undefined) { + for (sid in this.sessions) { + if (this.sessions[sid].state != 'ended') { + this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text); + this.sessions[sid].terminate(); + } + delete this.jid2session[this.sessions[sid].peerjid]; + delete this.sessions[sid]; + } + } else if (this.sessions.hasOwnProperty(sid)) { + if (this.sessions[sid].state != 'ended') { + this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text); + this.sessions[sid].terminate(); + } + delete this.jid2session[this.sessions[sid].peerjid]; + delete this.sessions[sid]; + } + }, + // Used to terminate a session when an unavailable presence is received. + terminateByJid: function (jid) { + if (this.jid2session.hasOwnProperty(jid)) { + var sess = this.jid2session[jid]; + if (sess) { + sess.terminate(); + console.log('peer went away silently', jid); + delete this.sessions[sess.sid]; + delete this.jid2session[jid]; + $(document).trigger('callterminated.jingle', + [sess.sid, jid], 'gone'); + } + } + }, + terminateRemoteByJid: function (jid, reason) { + if (this.jid2session.hasOwnProperty(jid)) { + var sess = this.jid2session[jid]; + if (sess) { + sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null); + sess.terminate(); + console.log('terminate peer with jid', sess.sid, jid); + delete this.sessions[sess.sid]; + delete this.jid2session[jid]; + $(document).trigger('callterminated.jingle', + [sess.sid, jid, 'kicked']); + } + } + }, + getStunAndTurnCredentials: function () { + // get stun and turn configuration from server via xep-0215 + // uses time-limited credentials as described in + // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 + // + // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua + // for a prosody module which implements this + // + // currently, this doesn't work with updateIce and therefore credentials with a long + // validity have to be fetched before creating the peerconnection + // TODO: implement refresh via updateIce as described in + // https://code.google.com/p/webrtc/issues/detail?id=1650 + var self = this; + this.connection.sendIQ( + $iq({type: 'get', to: this.connection.domain}) + .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}), + function (res) { + var iceservers = []; + $(res).find('>services>service').each(function (idx, el) { + el = $(el); + var dict = {}; + var type = el.attr('type'); + switch (type) { + case 'stun': + dict.url = 'stun:' + el.attr('host'); + if (el.attr('port')) { + dict.url += ':' + el.attr('port'); + } + iceservers.push(dict); + break; + case 'turn': + case 'turns': + dict.url = type + ':'; + if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508 + if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) { + dict.url += el.attr('username') + '@'; + } else { + dict.username = el.attr('username'); // only works in M28 + } + } + dict.url += el.attr('host'); + if (el.attr('port') && el.attr('port') != '3478') { + dict.url += ':' + el.attr('port'); + } + if (el.attr('transport') && el.attr('transport') != 'udp') { + dict.url += '?transport=' + el.attr('transport'); + } + if (el.attr('password')) { + dict.credential = el.attr('password'); + } + iceservers.push(dict); + break; + } + }); + self.ice_config.iceServers = iceservers; + }, + function (err) { + console.warn('getting turn credentials failed', err); + console.warn('is mod_turncredentials or similar installed?'); + } + ); + // implement push? + }, + + /** + * Populates the log data + */ + populateData: function () { + var data = {}; + Object.keys(this.sessions).forEach(function (sid) { + var session = this.sessions[sid]; + if (session.peerconnection && session.peerconnection.updateLog) { + // FIXME: should probably be a .dump call + data["jingle_" + session.sid] = { + updateLog: session.peerconnection.updateLog, + stats: session.peerconnection.stats, + url: window.location.href + }; + } + }); + return data; + } + }); +}; + diff --git a/modules/xmpp/strophe.logger.js b/modules/xmpp/strophe.logger.js new file mode 100644 index 000000000..4866ff89b --- /dev/null +++ b/modules/xmpp/strophe.logger.js @@ -0,0 +1,20 @@ +/* global Strophe */ +module.exports = function () { + + Strophe.addConnectionPlugin('logger', { + // logs raw stanzas and makes them available for download as JSON + connection: null, + log: [], + init: function (conn) { + this.connection = conn; + this.connection.rawInput = this.log_incoming.bind(this); + this.connection.rawOutput = this.log_outgoing.bind(this); + }, + log_incoming: function (stanza) { + this.log.push([new Date().getTime(), 'incoming', stanza]); + }, + log_outgoing: function (stanza) { + this.log.push([new Date().getTime(), 'outgoing', stanza]); + } + }); +}; \ No newline at end of file diff --git a/modules/xmpp/strophe.moderate.js b/modules/xmpp/strophe.moderate.js new file mode 100644 index 000000000..64a8bccfa --- /dev/null +++ b/modules/xmpp/strophe.moderate.js @@ -0,0 +1,58 @@ +/* global $, $iq, config, connection, focusMucJid, forceMuted, + setAudioMuted, Strophe */ +/** + * Moderate connection plugin. + */ +module.exports = function (XMPP) { + Strophe.addConnectionPlugin('moderate', { + connection: null, + init: function (conn) { + this.connection = conn; + + this.connection.addHandler(this.onMute.bind(this), + 'http://jitsi.org/jitmeet/audio', + 'iq', + 'set', + null, + null); + }, + setMute: function (jid, mute) { + console.info("set mute", mute); + var iqToFocus = $iq({to: focusMucJid, type: 'set'}) + .c('mute', { + xmlns: 'http://jitsi.org/jitmeet/audio', + jid: jid + }) + .t(mute.toString()) + .up(); + + this.connection.sendIQ( + iqToFocus, + function (result) { + console.log('set mute', result); + }, + function (error) { + console.log('set mute error', error); + }); + }, + onMute: function (iq) { + var from = iq.getAttribute('from'); + if (from !== focusMucJid) { + console.warn("Ignored mute from non focus peer"); + return false; + } + var mute = $(iq).find('mute'); + if (mute.length) { + var doMuteAudio = mute.text() === "true"; + UI.setAudioMuted(doMuteAudio); + XMPP.forceMuted = doMuteAudio; + } + return true; + }, + eject: function (jid) { + // We're not the focus, so can't terminate + //connection.jingle.terminateRemoteByJid(jid, 'kick'); + this.connection.emuc.kick(jid); + } + }); +} \ No newline at end of file diff --git a/modules/xmpp/strophe.rayo.js b/modules/xmpp/strophe.rayo.js new file mode 100644 index 000000000..9d0db5547 --- /dev/null +++ b/modules/xmpp/strophe.rayo.js @@ -0,0 +1,95 @@ +/* jshint -W117 */ +module.exports = function() { + Strophe.addConnectionPlugin('rayo', + { + RAYO_XMLNS: 'urn:xmpp:rayo:1', + connection: null, + init: function (conn) { + this.connection = conn; + if (this.connection.disco) { + this.connection.disco.addFeature('urn:xmpp:rayo:client:1'); + } + + this.connection.addHandler( + this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null); + }, + onRayo: function (iq) { + console.info("Rayo IQ", iq); + }, + dial: function (to, from, roomName, roomPass) { + var self = this; + var req = $iq( + { + type: 'set', + to: focusMucJid + } + ); + req.c('dial', + { + xmlns: this.RAYO_XMLNS, + to: to, + from: from + }); + req.c('header', + { + name: 'JvbRoomName', + value: roomName + }).up(); + + if (roomPass !== null && roomPass.length) { + + req.c('header', + { + name: 'JvbRoomPassword', + value: roomPass + }).up(); + } + + this.connection.sendIQ( + req, + function (result) { + console.info('Dial result ', result); + + var resource = $(result).find('ref').attr('uri'); + this.call_resource = resource.substr('xmpp:'.length); + console.info( + "Received call resource: " + this.call_resource); + }, + function (error) { + console.info('Dial error ', error); + } + ); + }, + hang_up: function () { + if (!this.call_resource) { + console.warn("No call in progress"); + return; + } + + var self = this; + var req = $iq( + { + type: 'set', + to: this.call_resource + } + ); + req.c('hangup', + { + xmlns: this.RAYO_XMLNS + }); + + this.connection.sendIQ( + req, + function (result) { + console.info('Hangup result ', result); + self.call_resource = null; + }, + function (error) { + console.info('Hangup error ', error); + self.call_resource = null; + } + ); + } + } + ); +}; diff --git a/modules/xmpp/strophe.util.js b/modules/xmpp/strophe.util.js new file mode 100644 index 000000000..b7d834828 --- /dev/null +++ b/modules/xmpp/strophe.util.js @@ -0,0 +1,42 @@ +/** + * Strophe logger implementation. Logs from level WARN and above. + */ +module.exports = function () { + + Strophe.log = function (level, msg) { + switch (level) { + case Strophe.LogLevel.WARN: + console.warn("Strophe: " + msg); + break; + case Strophe.LogLevel.ERROR: + case Strophe.LogLevel.FATAL: + console.error("Strophe: " + msg); + break; + } + }; + + Strophe.getStatusString = function (status) { + switch (status) { + case Strophe.Status.ERROR: + return "ERROR"; + case Strophe.Status.CONNECTING: + return "CONNECTING"; + case Strophe.Status.CONNFAIL: + return "CONNFAIL"; + case Strophe.Status.AUTHENTICATING: + return "AUTHENTICATING"; + case Strophe.Status.AUTHFAIL: + return "AUTHFAIL"; + case Strophe.Status.CONNECTED: + return "CONNECTED"; + case Strophe.Status.DISCONNECTED: + return "DISCONNECTED"; + case Strophe.Status.DISCONNECTING: + return "DISCONNECTING"; + case Strophe.Status.ATTACHED: + return "ATTACHED"; + default: + return "unknown"; + } + }; +}; diff --git a/modules/xmpp/xmpp.js b/modules/xmpp/xmpp.js new file mode 100644 index 000000000..d7ad4d2dc --- /dev/null +++ b/modules/xmpp/xmpp.js @@ -0,0 +1,422 @@ +var Moderator = require("./moderator"); +var EventEmitter = require("events"); +var Recording = require("./recording"); +var SDP = require("./SDP"); + +var eventEmitter = new EventEmitter(); +var connection = null; +var authenticatedUser = false; +var activecall = null; + +function connect(jid, password, uiCredentials) { + var bosh + = uiCredentials.bosh || config.bosh || '/http-bind'; + connection = new Strophe.Connection(bosh); + Moderator.setConnection(connection); + + var settings = UI.getSettings(); + var email = settings.email; + var displayName = settings.displayName; + if(email) { + connection.emuc.addEmailToPresence(email); + } else { + connection.emuc.addUserIdToPresence(settings.uid); + } + if(displayName) { + connection.emuc.addDisplayNameToPresence(displayName); + } + + if (connection.disco) { + // for chrome, add multistream cap + } + connection.jingle.pc_constraints = RTC.getPCConstraints(); + if (config.useIPv6) { + // https://code.google.com/p/webrtc/issues/detail?id=2828 + if (!connection.jingle.pc_constraints.optional) + connection.jingle.pc_constraints.optional = []; + connection.jingle.pc_constraints.optional.push({googIPv6: true}); + } + + if(!password) + password = uiCredentials.password; + + var anonymousConnectionFailed = false; + connection.connect(jid, password, function (status, msg) { + console.log('Strophe status changed to', + Strophe.getStatusString(status)); + if (status === Strophe.Status.CONNECTED) { + if (config.useStunTurn) { + connection.jingle.getStunAndTurnCredentials(); + } + UI.disableConnect(); + + console.info("My Jabber ID: " + connection.jid); + + if(password) + authenticatedUser = true; + maybeDoJoin(); + } else if (status === Strophe.Status.CONNFAIL) { + if(msg === 'x-strophe-bad-non-anon-jid') { + anonymousConnectionFailed = true; + } + } else if (status === Strophe.Status.DISCONNECTED) { + if(anonymousConnectionFailed) { + // prompt user for username and password + XMPP.promptLogin(); + } + } else if (status === Strophe.Status.AUTHFAIL) { + // wrong password or username, prompt user + XMPP.promptLogin(); + + } + }); +} + + + +function maybeDoJoin() { + if (connection && connection.connected && + Strophe.getResourceFromJid(connection.jid) + && (RTC.localAudio || RTC.localVideo)) { + // .connected is true while connecting? + doJoin(); + } +} + +function doJoin() { + var roomName = UI.generateRoomName(); + + Moderator.allocateConferenceFocus( + roomName, UI.checkForNicknameAndJoin); +} + +function initStrophePlugins() +{ + require("./strophe.emuc")(XMPP, eventEmitter); + require("./strophe.jingle")(); + require("./strophe.moderate")(XMPP); + require("./strophe.util")(); + require("./strophe.rayo")(); + require("./strophe.logger")(); +} + +function registerListeners() { + RTC.addStreamListener(maybeDoJoin, + StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); +} + +function setupEvents() { + $(window).bind('beforeunload', function () { + if (connection && connection.connected) { + // ensure signout + $.ajax({ + type: 'POST', + url: config.bosh, + async: false, + cache: false, + contentType: 'application/xml', + data: "" + + "" + + "", + success: function (data) { + console.log('signed out'); + console.log(data); + }, + error: function (XMLHttpRequest, textStatus, errorThrown) { + console.log('signout error', + textStatus + ' (' + errorThrown + ')'); + } + }); + } + XMPP.disposeConference(true); + }); +} + +var XMPP = { + sessionTerminated: false, + /** + * Remembers if we were muted by the focus. + * @type {boolean} + */ + forceMuted: false, + start: function (uiCredentials) { + setupEvents(); + initStrophePlugins(); + registerListeners(); + Moderator.init(); + var jid = uiCredentials.jid || + config.hosts.anonymousdomain || + config.hosts.domain || + window.location.hostname; + connect(jid, null, uiCredentials); + }, + promptLogin: function () { + UI.showLoginPopup(connect); + }, + joinRooom: function(roomName, useNicks, nick) + { + var roomjid; + roomjid = roomName; + + if (useNicks) { + if (nick) { + roomjid += '/' + nick; + } else { + roomjid += '/' + Strophe.getNodeFromJid(connection.jid); + } + } else { + + var tmpJid = Strophe.getNodeFromJid(connection.jid); + + if(!authenticatedUser) + tmpJid = tmpJid.substr(0, 8); + + roomjid += '/' + tmpJid; + } + connection.emuc.doJoin(roomjid); + }, + myJid: function () { + if(!connection) + return null; + return connection.emuc.myroomjid; + }, + myResource: function () { + if(!connection || ! connection.emuc.myroomjid) + return null; + return Strophe.getResourceFromJid(connection.emuc.myroomjid); + }, + disposeConference: function (onUnload) { + eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload); + var handler = activecall; + if (handler && handler.peerconnection) { + // FIXME: probably removing streams is not required and close() should + // be enough + if (RTC.localAudio) { + handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload); + } + if (RTC.localVideo) { + handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload); + } + handler.peerconnection.close(); + } + activecall = null; + if(!onUnload) + { + this.sessionTerminated = true; + connection.emuc.doLeave(); + } + }, + addListener: function(type, listener) + { + eventEmitter.on(type, listener); + }, + removeListener: function (type, listener) { + eventEmitter.removeListener(type, listener); + }, + allocateConferenceFocus: function(roomName, callback) { + Moderator.allocateConferenceFocus(roomName, callback); + }, + isModerator: function () { + return Moderator.isModerator(); + }, + isSipGatewayEnabled: function () { + return Moderator.isSipGatewayEnabled(); + }, + isExternalAuthEnabled: function () { + return Moderator.isExternalAuthEnabled(); + }, + switchStreams: function (stream, oldStream, callback) { + if (activecall) { + // FIXME: will block switchInProgress on true value in case of exception + activecall.switchStreams(stream, oldStream, callback); + } else { + // We are done immediately + console.error("No conference handler"); + UI.messageHandler.showError('Error', + 'Unable to switch video stream.'); + callback(); + } + }, + setVideoMute: function (mute, callback, options) { + if(activecall && connection && RTC.localVideo) + { + activecall.setVideoMute(mute, callback, options); + } + }, + setAudioMute: function (mute, callback) { + if (!(connection && RTC.localAudio)) { + return false; + } + + + if (this.forceMuted && !mute) { + console.info("Asking focus for unmute"); + connection.moderate.setMute(connection.emuc.myroomjid, mute); + // FIXME: wait for result before resetting muted status + this.forceMuted = false; + } + + if (mute == RTC.localAudio.isMuted()) { + // Nothing to do + return true; + } + + // It is not clear what is the right way to handle multiple tracks. + // So at least make sure that they are all muted or all unmuted and + // that we send presence just once. + RTC.localAudio.mute(); + // isMuted is the opposite of audioEnabled + connection.emuc.addAudioInfoToPresence(mute); + connection.emuc.sendPresence(); + callback(); + return true; + }, + // Really mute video, i.e. dont even send black frames + muteVideo: function (pc, unmute) { + // FIXME: this probably needs another of those lovely state safeguards... + // which checks for iceconn == connected and sigstate == stable + pc.setRemoteDescription(pc.remoteDescription, + function () { + pc.createAnswer( + function (answer) { + var sdp = new SDP(answer.sdp); + if (sdp.media.length > 1) { + if (unmute) + sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv'); + else + sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly'); + sdp.raw = sdp.session + sdp.media.join(''); + answer.sdp = sdp.raw; + } + pc.setLocalDescription(answer, + function () { + console.log('mute SLD ok'); + }, + function (error) { + console.log('mute SLD error'); + UI.messageHandler.showError('Error', + 'Oops! Something went wrong and we failed to ' + + 'mute! (SLD Failure)'); + } + ); + }, + function (error) { + console.log(error); + UI.messageHandler.showError(); + } + ); + }, + function (error) { + console.log('muteVideo SRD error'); + UI.messageHandler.showError('Error', + 'Oops! Something went wrong and we failed to stop video!' + + '(SRD Failure)'); + + } + ); + }, + toggleRecording: function (tokenEmptyCallback, + startingCallback, startedCallback) { + Recording.toggleRecording(tokenEmptyCallback, + startingCallback, startedCallback); + }, + addToPresence: function (name, value, dontSend) { + switch (name) + { + case "displayName": + connection.emuc.addDisplayNameToPresence(value); + break; + case "etherpad": + connection.emuc.addEtherpadToPresence(value); + break; + case "prezi": + connection.emuc.addPreziToPresence(value, 0); + break; + case "preziSlide": + connection.emuc.addCurrentSlideToPresence(value); + break; + case "connectionQuality": + connection.emuc.addConnectionInfoToPresence(value); + break; + case "email": + connection.emuc.addEmailToPresence(value); + default : + console.log("Unknown tag for presence."); + return; + } + if(!dontSend) + connection.emuc.sendPresence(); + }, + sendLogs: function (data) { + if(!focusMucJid) + return; + + var deflate = true; + + var content = JSON.stringify(dataYes); + if (deflate) { + content = String.fromCharCode.apply(null, Pako.deflateRaw(content)); + } + content = Base64.encode(content); + // XEP-0337-ish + var message = $msg({to: focusMucJid, type: 'normal'}); + message.c('log', { xmlns: 'urn:xmpp:eventlog', + id: 'PeerConnectionStats'}); + message.c('message').t(content).up(); + if (deflate) { + message.c('tag', {name: "deflated", value: "true"}).up(); + } + message.up(); + + connection.send(message); + }, + populateData: function () { + var data = {}; + if (connection.jingle) { + data = connection.jingle.populateData(); + } + return data; + }, + getLogger: function () { + if(connection.logger) + return connection.logger.log; + return null; + }, + getPrezi: function () { + return connection.emuc.getPrezi(this.myJid()); + }, + removePreziFromPresence: function () { + connection.emuc.removePreziFromPresence(); + connection.emuc.sendPresence(); + }, + sendChatMessage: function (message, nickname) { + connection.emuc.sendMessage(message, nickname); + }, + setSubject: function (topic) { + connection.emuc.setSubject(topic); + }, + lockRoom: function (key, onSuccess, onError, onNotSupported) { + connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported); + }, + dial: function (to, from, roomName,roomPass) { + connection.rayo.dial(to, from, roomName,roomPass); + }, + setMute: function (jid, mute) { + connection.moderate.setMute(jid, mute); + }, + eject: function (jid) { + connection.moderate.eject(jid); + }, + findJidFromResource: function (resource) { + connection.emuc.findJidFromResource(resource); + }, + getMembers: function () { + return connection.emuc.members; + } + +}; + +module.exports = XMPP; \ No newline at end of file diff --git a/muc.js b/muc.js deleted file mode 100644 index 98b347337..000000000 --- a/muc.js +++ /dev/null @@ -1,548 +0,0 @@ -/* jshint -W117 */ -/* a simple MUC connection plugin - * can only handle a single MUC room - */ -Strophe.addConnectionPlugin('emuc', { - connection: null, - roomjid: null, - myroomjid: null, - members: {}, - list_members: [], // so we can elect a new focus - presMap: {}, - preziMap: {}, - joined: false, - isOwner: false, - role: null, - init: function (conn) { - this.connection = conn; - }, - initPresenceMap: function (myroomjid) { - this.presMap['to'] = myroomjid; - this.presMap['xns'] = 'http://jabber.org/protocol/muc'; - }, - doJoin: function (jid, password) { - this.myroomjid = jid; - - console.info("Joined MUC as " + this.myroomjid); - - this.initPresenceMap(this.myroomjid); - - if (!this.roomjid) { - this.roomjid = Strophe.getBareJidFromJid(jid); - // add handlers (just once) - this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true}); - this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true}); - this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true}); - this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true}); - } - if (password !== undefined) { - this.presMap['password'] = password; - } - this.sendPresence(); - }, - doLeave: function() { - console.log("do leave", this.myroomjid); - var pres = $pres({to: this.myroomjid, type: 'unavailable' }); - this.presMap.length = 0; - this.connection.send(pres); - }, - createNonAnonymousRoom: function() { - // http://xmpp.org/extensions/xep-0045.html#createroom-reserved - - var getForm = $iq({type: 'get', to: this.roomjid}) - .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}) - .c('x', {xmlns: 'jabber:x:data', type: 'submit'}); - - this.connection.sendIQ(getForm, function (form){ - - if (!$(form).find( - '>query>x[xmlns="jabber:x:data"]' + - '>field[var="muc#roomconfig_whois"]').length) { - - console.error('non-anonymous rooms not supported'); - return; - } - - var formSubmit = $iq({to: this.roomjid, type: 'set'}) - .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}); - - formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'}); - - formSubmit.c('field', {'var': 'FORM_TYPE'}) - .c('value') - .t('http://jabber.org/protocol/muc#roomconfig').up().up(); - - formSubmit.c('field', {'var': 'muc#roomconfig_whois'}) - .c('value').t('anyone').up().up(); - - this.connection.sendIQ(formSubmit); - - }, function (error){ - console.error("Error getting room configuration form"); - }); - }, - onPresence: function (pres) { - var from = pres.getAttribute('from'); - - // What is this for? A workaround for something? - if (pres.getAttribute('type')) { - return true; - } - - // Parse etherpad tag. - var etherpad = $(pres).find('>etherpad'); - if (etherpad.length) { - if (config.etherpad_base && !Moderator.isModerator()) { - UI.initEtherpad(etherpad.text()); - } - } - - // Parse prezi tag. - var presentation = $(pres).find('>prezi'); - if (presentation.length) - { - var url = presentation.attr('url'); - var current = presentation.find('>current').text(); - - console.log('presentation info received from', from, url); - - if (this.preziMap[from] == null) { - this.preziMap[from] = url; - - $(document).trigger('presentationadded.muc', [from, url, current]); - } - else { - $(document).trigger('gotoslide.muc', [from, url, current]); - } - } - else if (this.preziMap[from] != null) { - var url = this.preziMap[from]; - delete this.preziMap[from]; - $(document).trigger('presentationremoved.muc', [from, url]); - } - - // Parse audio info tag. - var audioMuted = $(pres).find('>audiomuted'); - if (audioMuted.length) { - $(document).trigger('audiomuted.muc', [from, audioMuted.text()]); - } - - // Parse video info tag. - var videoMuted = $(pres).find('>videomuted'); - if (videoMuted.length) { - $(document).trigger('videomuted.muc', [from, videoMuted.text()]); - } - - var stats = $(pres).find('>stats'); - if(stats.length) - { - var statsObj = {}; - Strophe.forEachChild(stats[0], "stat", function (el) { - statsObj[el.getAttribute("name")] = el.getAttribute("value"); - }); - connectionquality.updateRemoteStats(from, statsObj); - } - - // Parse status. - if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) { - this.isOwner = true; - this.createNonAnonymousRoom(); - } - - // Parse roles. - var member = {}; - member.show = $(pres).find('>show').text(); - member.status = $(pres).find('>status').text(); - var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item'); - member.affiliation = tmp.attr('affiliation'); - member.role = tmp.attr('role'); - - // Focus recognition - member.jid = tmp.attr('jid'); - member.isFocus = false; - if (member.jid - && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) { - member.isFocus = true; - } - - var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]'); - member.displayName = (nicktag.length > 0 ? nicktag.html() : null); - - if (from == this.myroomjid) { - if (member.affiliation == 'owner') this.isOwner = true; - if (this.role !== member.role) { - this.role = member.role; - if(Moderator.onLocalRoleChange) - Moderator.onLocalRoleChange(from, member, pres); - UI.onLocalRoleChange(from, member, pres); - } - if (!this.joined) { - this.joined = true; - $(document).trigger('joined.muc', [from, member]); - UI.onMucJoined(from, member); - this.list_members.push(from); - } - } else if (this.members[from] === undefined) { - // new participant - this.members[from] = member; - this.list_members.push(from); - console.log('entered', from, member); - if (member.isFocus) - { - focusMucJid = from; - console.info("Ignore focus: " + from +", real JID: " + member.jid); - } - else { - var id = $(pres).find('>userID').text(); - var email = $(pres).find('>email'); - if (email.length > 0) { - id = email.text(); - } - UI.onMucEntered(from, id, member.displayName); - API.triggerEvent("participantJoined",{jid: from}); - } - } else { - // Presence update for existing participant - // Watch role change: - if (this.members[from].role != member.role) { - this.members[from].role = member.role; - UI.onMucRoleChanged(member.role, member.displayName); - } - } - - // Always trigger presence to update bindings - $(document).trigger('presence.muc', [from, member, pres]); - - // Trigger status message update - if (member.status) { - UI.onMucPresenceStatus(from, member); - } - - return true; - }, - onPresenceUnavailable: function (pres) { - var from = pres.getAttribute('from'); - // Status code 110 indicates that this notification is "self-presence". - if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) { - delete this.members[from]; - this.list_members.splice(this.list_members.indexOf(from), 1); - this.onParticipantLeft(from); - } - // If the status code is 110 this means we're leaving and we would like - // to remove everyone else from our view, so we trigger the event. - else if (this.list_members.length > 1) { - for (var i = 0; i < this.list_members.length; i++) { - var member = this.list_members[i]; - delete this.members[i]; - this.list_members.splice(i, 1); - this.onParticipantLeft(member); - } - } - if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) { - $(document).trigger('kicked.muc', [from]); - } - return true; - }, - onPresenceError: function (pres) { - var from = pres.getAttribute('from'); - if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { - console.log('on password required', from); - - UI.onPasswordReqiured(function (value) { - connection.emuc.doJoin(from, value); - }) - } else if ($(pres).find( - '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { - var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to')); - if(toDomain === config.hosts.anonymousdomain) { - // we are connected with anonymous domain and only non anonymous users can create rooms - // we must authorize the user - $(document).trigger('passwordrequired.main'); - } else { - console.warn('onPresError ', pres); - UI.messageHandler.openReportDialog(null, - 'Oops! Something went wrong and we couldn`t connect to the conference.', - pres); - } - } else { - console.warn('onPresError ', pres); - UI.messageHandler.openReportDialog(null, - 'Oops! Something went wrong and we couldn`t connect to the conference.', - pres); - } - return true; - }, - sendMessage: function (body, nickname) { - var msg = $msg({to: this.roomjid, type: 'groupchat'}); - msg.c('body', body).up(); - if (nickname) { - msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up(); - } - this.connection.send(msg); - API.triggerEvent("outgoingMessage", {"message": body}); - }, - setSubject: function (subject){ - var msg = $msg({to: this.roomjid, type: 'groupchat'}); - msg.c('subject', subject); - this.connection.send(msg); - console.log("topic changed to " + subject); - }, - onMessage: function (msg) { - // FIXME: this is a hack. but jingle on muc makes nickchanges hard - var from = msg.getAttribute('from'); - var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from); - - var txt = $(msg).find('>body').text(); - var type = msg.getAttribute("type"); - if(type == "error") - { - UI.chatAddError($(msg).find('>text').text(), txt); - return true; - } - - var subject = $(msg).find('>subject'); - if(subject.length) - { - var subjectText = subject.text(); - if(subjectText || subjectText == "") { - UI.chatSetSubject(subjectText); - console.log("Subject is changed to " + subjectText); - } - } - - - if (txt) { - console.log('chat', nick, txt); - UI.updateChatConversation(from, nick, txt); - if(from != this.myroomjid) - API.triggerEvent("incomingMessage", - {"from": from, "nick": nick, "message": txt}); - } - return true; - }, - lockRoom: function (key, onSuccess, onError, onNotSupported) { - //http://xmpp.org/extensions/xep-0045.html#roomconfig - var ob = this; - this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}), - function (res) { - if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) { - var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}); - formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'}); - formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up(); - formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up(); - // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373 - formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up(); - // FIXME: is muc#roomconfig_passwordprotectedroom required? - this.connection.sendIQ(formsubmit, - onSuccess, - onError); - } else { - onNotSupported(); - } - }, onError); - }, - kick: function (jid) { - var kickIQ = $iq({to: this.roomjid, type: 'set'}) - .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'}) - .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'}) - .c('reason').t('You have been kicked.').up().up().up(); - - this.connection.sendIQ( - kickIQ, - function (result) { - console.log('Kick participant with jid: ', jid, result); - }, - function (error) { - console.log('Kick participant error: ', error); - }); - }, - sendPresence: function () { - var pres = $pres({to: this.presMap['to'] }); - pres.c('x', {xmlns: this.presMap['xns']}); - - if (this.presMap['password']) { - pres.c('password').t(this.presMap['password']).up(); - } - - pres.up(); - - // Send XEP-0115 'c' stanza that contains our capabilities info - if (connection.caps) { - connection.caps.node = config.clientNode; - pres.c('c', connection.caps.generateCapsAttrs()).up(); - } - - pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'}) - .t(navigator.userAgent).up(); - - if(this.presMap['bridgeIsDown']) { - pres.c('bridgeIsDown').up(); - } - - if(this.presMap['email']) { - pres.c('email').t(this.presMap['email']).up(); - } - - if(this.presMap['userId']) { - pres.c('userId').t(this.presMap['userId']).up(); - } - - if (this.presMap['displayName']) { - // XEP-0172 - pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}) - .t(this.presMap['displayName']).up(); - } - - if (this.presMap['audions']) { - pres.c('audiomuted', {xmlns: this.presMap['audions']}) - .t(this.presMap['audiomuted']).up(); - } - - if (this.presMap['videons']) { - pres.c('videomuted', {xmlns: this.presMap['videons']}) - .t(this.presMap['videomuted']).up(); - } - - if(this.presMap['statsns']) - { - var stats = pres.c('stats', {xmlns: this.presMap['statsns']}); - for(var stat in this.presMap["stats"]) - if(this.presMap["stats"][stat] != null) - stats.c("stat",{name: stat, value: this.presMap["stats"][stat]}).up(); - pres.up(); - } - - if (this.presMap['prezins']) { - pres.c('prezi', - {xmlns: this.presMap['prezins'], - 'url': this.presMap['preziurl']}) - .c('current').t(this.presMap['prezicurrent']).up().up(); - } - - if (this.presMap['etherpadns']) { - pres.c('etherpad', {xmlns: this.presMap['etherpadns']}) - .t(this.presMap['etherpadname']).up(); - } - - if (this.presMap['medians']) - { - pres.c('media', {xmlns: this.presMap['medians']}); - var sourceNumber = 0; - Object.keys(this.presMap).forEach(function (key) { - if (key.indexOf('source') >= 0) { - sourceNumber++; - } - }); - if (sourceNumber > 0) - for (var i = 1; i <= sourceNumber/3; i ++) { - pres.c('source', - {type: this.presMap['source' + i + '_type'], - ssrc: this.presMap['source' + i + '_ssrc'], - direction: this.presMap['source'+ i + '_direction'] - || 'sendrecv' } - ).up(); - } - } - - pres.up(); -// console.debug(pres.toString()); - connection.send(pres); - }, - addDisplayNameToPresence: function (displayName) { - this.presMap['displayName'] = displayName; - }, - addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) { - if (!this.presMap['medians']) - this.presMap['medians'] = 'http://estos.de/ns/mjs'; - - this.presMap['source' + sourceNumber + '_type'] = mtype; - this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs; - this.presMap['source' + sourceNumber + '_direction'] = direction; - }, - clearPresenceMedia: function () { - var self = this; - Object.keys(this.presMap).forEach( function(key) { - if(key.indexOf('source') != -1) { - delete self.presMap[key]; - } - }); - }, - addPreziToPresence: function (url, currentSlide) { - this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi'; - this.presMap['preziurl'] = url; - this.presMap['prezicurrent'] = currentSlide; - }, - removePreziFromPresence: function () { - delete this.presMap['prezins']; - delete this.presMap['preziurl']; - delete this.presMap['prezicurrent']; - }, - addCurrentSlideToPresence: function (currentSlide) { - this.presMap['prezicurrent'] = currentSlide; - }, - getPrezi: function (roomjid) { - return this.preziMap[roomjid]; - }, - addEtherpadToPresence: function(etherpadName) { - this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad'; - this.presMap['etherpadname'] = etherpadName; - }, - addAudioInfoToPresence: function(isMuted) { - this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio'; - this.presMap['audiomuted'] = isMuted.toString(); - }, - addVideoInfoToPresence: function(isMuted) { - this.presMap['videons'] = 'http://jitsi.org/jitmeet/video'; - this.presMap['videomuted'] = isMuted.toString(); - }, - addConnectionInfoToPresence: function(stats) { - this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats'; - this.presMap['stats'] = stats; - }, - findJidFromResource: function(resourceJid) { - if(resourceJid && - resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)) { - return connection.emuc.myroomjid; - } - var peerJid = null; - Object.keys(this.members).some(function (jid) { - peerJid = jid; - return Strophe.getResourceFromJid(jid) === resourceJid; - }); - return peerJid; - }, - addBridgeIsDownToPresence: function() { - this.presMap['bridgeIsDown'] = true; - }, - addEmailToPresence: function(email) { - this.presMap['email'] = email; - }, - addUserIdToPresence: function(userId) { - this.presMap['userId'] = userId; - }, - isModerator: function() { - return this.role === 'moderator'; - }, - getMemberRole: function(peerJid) { - if (this.members[peerJid]) { - return this.members[peerJid].role; - } - return null; - }, - onParticipantLeft: function (jid) { - UI.onMucLeft(jid); - - API.triggerEvent("participantLeft",{jid: jid}); - - delete jid2Ssrc[jid]; - - connection.jingle.terminateByJid(jid); - - if (connection.emuc.getPrezi(jid)) { - $(document).trigger('presentationremoved.muc', - [jid, connection.emuc.getPrezi(jid)]); - } - - Moderator.onMucLeft(jid); - } -}); diff --git a/recording.js b/recording.js deleted file mode 100644 index d60402582..000000000 --- a/recording.js +++ /dev/null @@ -1,167 +0,0 @@ -/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator, - Toolbar, Util */ -var Recording = (function (my) { - var recordingToken = null; - var recordingEnabled; - - /** - * Whether to use a jirecon component for recording, or use the videobridge - * through COLIBRI. - */ - var useJirecon = (typeof config.hosts.jirecon != "undefined"); - - /** - * The ID of the jirecon recording session. Jirecon generates it when we - * initially start recording, and it needs to be used in subsequent requests - * to jirecon. - */ - var jireconRid = null; - - my.setRecordingToken = function (token) { - recordingToken = token; - }; - - my.setRecording = function (state, token, callback) { - if (useJirecon){ - this.setRecordingJirecon(state, token, callback); - } else { - this.setRecordingColibri(state, token, callback); - } - }; - - my.setRecordingJirecon = function (state, token, callback) { - if (state == recordingEnabled){ - return; - } - - var iq = $iq({to: config.hosts.jirecon, type: 'set'}) - .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon', - action: state ? 'start' : 'stop', - mucjid: connection.emuc.roomjid}); - if (!state){ - iq.attrs({rid: jireconRid}); - } - - console.log('Start recording'); - - connection.sendIQ( - iq, - function (result) { - // TODO wait for an IQ with the real status, since this is - // provisional? - jireconRid = $(result).find('recording').attr('rid'); - console.log('Recording ' + (state ? 'started' : 'stopped') + - '(jirecon)' + result); - recordingEnabled = state; - if (!state){ - jireconRid = null; - } - - callback(state); - }, - function (error) { - console.log('Failed to start recording, error: ', error); - callback(recordingEnabled); - }); - }; - - // Sends a COLIBRI message which enables or disables (according to 'state') - // the recording on the bridge. Waits for the result IQ and calls 'callback' - // with the new recording state, according to the IQ. - my.setRecordingColibri = function (state, token, callback) { - var elem = $iq({to: focusMucJid, type: 'set'}); - elem.c('conference', { - xmlns: 'http://jitsi.org/protocol/colibri' - }); - elem.c('recording', {state: state, token: token}); - - connection.sendIQ(elem, - function (result) { - console.log('Set recording "', state, '". Result:', result); - var recordingElem = $(result).find('>conference>recording'); - var newState = ('true' === recordingElem.attr('state')); - - recordingEnabled = newState; - callback(newState); - }, - function (error) { - console.warn(error); - callback(recordingEnabled); - } - ); - }; - - my.toggleRecording = function () { - if (!Moderator.isModerator()) { - console.log( - 'non-focus, or conference not yet organized:' + - ' not enabling recording'); - return; - } - - // Jirecon does not (currently) support a token. - if (!recordingToken && !useJirecon) - { - UI.messageHandler.openTwoButtonDialog(null, - '

Enter recording token

' + - '', - false, - "Save", - function (e, v, m, f) { - if (v) { - var token = document.getElementById('recordingToken'); - - if (token.value) { - my.setRecordingToken( - Util.escapeHtml(token.value)); - my.toggleRecording(); - } - } - }, - function (event) { - document.getElementById('recordingToken').focus(); - }, - function () {} - ); - - return; - } - - var oldState = recordingEnabled; - UI.setRecordingButtonState(!oldState); - my.setRecording(!oldState, - recordingToken, - function (state) { - console.log("New recording state: ", state); - if (state === oldState) - { - // FIXME: new focus: - // this will not work when moderator changes - // during active session. Then it will assume that - // recording status has changed to true, but it might have - // been already true(and we only received actual status from - // the focus). - // - // SO we start with status null, so that it is initialized - // here and will fail only after second click, so if invalid - // token was used we have to press the button twice before - // current status will be fetched and token will be reset. - // - // Reliable way would be to return authentication error. - // Or status update when moderator connects. - // Or we have to stop recording session when current - // moderator leaves the room. - - // Failed to change, reset the token because it might - // have been wrong - my.setRecordingToken(null); - } - // Update with returned status - UI.setRecordingButtonState(state); - } - ); - }; - - return my; -}(Recording || {})); diff --git a/service/xmpp/XMPPEvents.js b/service/xmpp/XMPPEvents.js new file mode 100644 index 000000000..ccc2e1d1b --- /dev/null +++ b/service/xmpp/XMPPEvents.js @@ -0,0 +1,14 @@ +var XMPPEvents = { + CONFERENCE_CERATED: "xmpp.conferenceCreated.jingle", + CALL_TERMINATED: "xmpp.callterminated.jingle", + CALL_INCOMING: "xmpp.callincoming.jingle", + DISPOSE_CONFERENCE: "xmpp.dispoce_confernce", + KICKED: "xmpp.kicked", + BRIDGE_DOWN: "xmpp.bridge_down", + USER_ID_CHANGED: "xmpp.user_id_changed", + CHANGED_STREAMS: "xmpp.changed_streams", + MUC_JOINED: "xmpp.muc_joined", + DISPLAY_NAME_CHANGED: "xmpp.display_name_changed", + REMOTE_STATS: "xmpp.remote_stats" +}; +//module.exports = XMPPEvents; \ No newline at end of file