UNPKG

@synerty/vortexjs

Version:

Custom observable data serialisation and routing based on Angular 2+

389 lines 60.7 kB
import { VortexClientABC, VortexClientStateE } from "./VortexClientABC"; import { VortexStateEnum } from "./VortexStatusService"; import { dateStr, getFiltStr } from "./UtilMisc"; import { PayloadEnvelope } from "./PayloadEnvelope"; import { Http } from "@capacitor-community/http"; import { Subject } from "rxjs"; // Delete this after upgrading to a newer RXJS with this method async function firstValueFrom(source$) { // This is a simplified version that might not have all // the subtle details of the full RxJS version, // but at its core this is the principle. return new Promise((resolve, reject) => { const subscription = source$.subscribe({ next(value) { resolve(value); subscription.unsubscribe(); }, error(err) { reject(err); }, }); }); } export class VortexClientCapacitorWebsocket extends VortexClientABC { lastReconnectDate = Date.parse("01-Jan-2017"); lastWebsocketId = null; unsentBuffer = []; // Make note of when we're reconnecting, // otherwise closing the last websocket causes a reconnection // call which then closes the current socket being open (in a loop) reconnectingInProgress = false; _data = ""; _vortexMsgsQueue = []; // True from the moment we call wsConnect until onClose fires. Lets // closeWebsocket await tear-down of a socket that's still mid-connect // (before lastWebsocketId is assigned), not just one that's online. hasActiveConnection = false; websocketCloseCompleteSubject = new Subject(); constructor(vortexStatusService, url, vortexClientName, headers) { super(vortexStatusService, url, vortexClientName, headers); } get isReady() { return this.isOnline; } // OVERRIDE Send send(payloadEnvelope) { if (!this.isReady) { throw new Error("Websocket vortex is not online."); } return super.send(payloadEnvelope); } // OVERRIDE reconnect async reconnect() { if (this.url == null) { this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.reconnect this.url is null, returning"); await new Promise((resolve) => setTimeout(resolve, 5000)); return; } this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.reconnect called"); if (this.reconnectingInProgress === true) { this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.reconnect returning #1"); return; } this.clearBeatTimer(); this._vortexState = VortexClientStateE.Idle; this.reconnectingInProgress = true; try { // We await this, to ensure we wait for the last // connecting before retrying. await this.createSocket(); } catch (e) { this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.reconnect" + ` Failed to connect websocket ${e}`); } this.reconnectingInProgress = false; this.restartTimer(); this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.reconnect returning #2"); } sendVortexMsg(vortexMsgs) { this.unsentBuffer.add(vortexMsgs); this.sendMessages(); } sendMessages() { while (this.unsentBuffer.length !== 0) { if (!this.isReady) return; const vortexMsg = this.unsentBuffer.shift(); CordovaWebsocketPlugin.wsSend(this.lastWebsocketId, vortexMsg + "."); } } _processData() { if (this._data) { let indexOfDot = this._data.indexOf("."); while (indexOfDot !== -1) { const vortexMsg = this._data.slice(0, indexOfDot); this._data = this._data.slice(indexOfDot + 1); if (vortexMsg.length !== 0) { this._vortexMsgsQueue.push(vortexMsg); } indexOfDot = this._data.indexOf("."); } } while (this._vortexMsgsQueue.length !== 0) { this._processVortexMsgs() // .catch((e) => console.log(`ERROR VortexClientCapacitorWebsocket: ${e}`)); } } async _processVortexMsgs() { while (this._vortexMsgsQueue.length !== 0) { const vortexMsg = this._vortexMsgsQueue.shift(); try { const payloadEnvelope = await PayloadEnvelope.fromVortexMsg(vortexMsg); this._deliverPayload(payloadEnvelope); } catch (e) { console.log(dateStr() + `ERROR: processing vortexMsg\n${e}\n${vortexMsg}`); } } } _deliverPayload(payloadEnvelope) { this.receive(payloadEnvelope); } async shutdown() { this.setShutdown(); this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.shutdown called"); await this.closeWebsocket(); this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.shutdown returned"); } async closeWebsocket() { this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.closeWebsocket called" + ` _vortexState=${this._vortexState}`); if (this.hasActiveConnection) { this.vortexStatusService.logDebug("Websocket close starting"); this.setClosing(); // If the plugin gave us an id, ask it to close. If not // (still mid-connect), we can only wait for the plugin's // own timeout to fire onClose. if (this.lastWebsocketId != null) { try { // SocketRocket asserts (aborts the process) on invalid // close codes. Always pass 1000 (normal close) and a // reason string so it's never nil/0 on the native side. CordovaWebsocketPlugin.wsClose(this.lastWebsocketId, 1000, "client closing"); } catch (e) { // pass } } this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.closeWebsocket waiting for confirmation"); const closed = await Promise.race([ firstValueFrom(this.websocketCloseCompleteSubject).then(() => true), new Promise((resolve) => setTimeout(() => resolve(false), 5000)), ]); if (!closed) { // The close callback never fired (e.g. socket already dead // with code 1006 before we called wsClose). Force cleanup. this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.closeWebsocket timed out" + " waiting for close — forcing cleanup"); this.hasActiveConnection = false; this.lastWebsocketId = null; this.setClosed(); } } else { this.setClosed(); } this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.closeWebsocket returning"); } async createSocket() { this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.createSocket called"); // If we're already connecting, then do nothing if (this.isConnecting) { if (!this.isShutdown) { this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.createSocket returning #1"); return; } try { console.log("Aborting WebSocket connection attempt," + " this is probably because of Vortex reconnection"); CordovaWebsocketPlugin.wsClose(this.lastWebsocketId, 1000, "aborting reconnect"); } catch (e) { // pass } } this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.createSocket Closing old websocket"); await this.closeWebsocket(); // Don't continually reconnect let reconnectDiffMs = Date.now() - this.lastReconnectDate; if (reconnectDiffMs < this.RECONNECT_BACKOFF_SECONDS * 1000) { this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.createSocket returning #2 - backoff" + " timeout"); setTimeout(() => this.createSocket(), this.RECONNECT_BACKOFF_SECONDS * 1000 - reconnectDiffMs + 10); return; } this.lastReconnectDate = Date.now(); // Prepare the args to send const args = { vortexUuid: this.uuid, vortexName: this.name, }; this.vortexStatusService.logConnectionInfo(`${dateStr()} WebSocket, connecting to ${this.url}`); const headersDict = {}; for (const key of this.headers.keys()) { headersDict[key] = this.headers.get(key); } console.log("Reconnecting with headers " + Object.keys(headersDict).join(", ")); await new Promise((_resolve, _reject) => { let promiseCalled = false; const resolver = (event) => { if (promiseCalled) return; promiseCalled = true; this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.createSocket calling resolve" + " from " + event); _resolve(); }; const rejector = (error, event) => { if (promiseCalled) return; promiseCalled = true; this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.createSocket calling reject" + " from '" + event + "' with message: " + error); _reject(error); }; this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.createSocket Creating socket"); const wsOptions = { url: this.url + getFiltStr(args), timeout: 120 * 1000, pingInterval: 60 * 1000, headers: headersDict, acceptAllCerts: false, }; this.hasActiveConnection = true; this.vortexStatusService.logDebug(`Connecting to Websocket with` + ` URL |${this.url}|` + ` Header ${JSON.stringify(headersDict)}`); CordovaWebsocketPlugin.wsConnect(wsOptions, (event) => { if (event["callbackMethod"] === "onMessage") { this.onMessage(event["message"], resolver); return; } if (event["callbackMethod"] === "onClose" || event["callbackMethod"] === "onFail") { this.onClose(event, rejector); return; } this.vortexStatusService.logDebug(`Received unknown callback from websocket` + `${JSON.stringify(event)}`); }, (event) => this.onConnect(event, resolver), (event) => { if ((event["exception"] || "").length !== 0) { this.onError(event, event["exception"], rejector); } this.onClose(event, rejector); }); }); this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.createSocket returning #3"); } onMessage(data, resolver) { if (!this.isOnline) { this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.onMessage setting online"); this.setOnline(); } resolver("onMessage"); this.beat(); if (data.length == null) { this.vortexStatusService.logDebug("WebSocket, We've received a websocket binary message," + " we expect a unicode"); return; } // If the server sends us a '.', that's a heart beat, return it. if (data === ".") { CordovaWebsocketPlugin.wsSend(this.lastWebsocketId, "."); return; } if (data[0] !== "{") { this._data += data; } else { if (data[data.length - 1] !== "}") { console.log(dateStr() + " ERROR, Payload should end with }," + " but it doesn't: " + data); return; } this._vortexMsgsQueue.push(data); } this._processData(); } onConnect(event, resolver) { this.lastWebsocketId = event["webSocketId"]; this.vortexStatusService.logDebug("VortexClientCapacitorWebsocket.onConnect Event"); if (!this.isOnline) { this.vortexStatusService.logInfo("VortexClientCapacitorWebsocket.onConnect setting online"); this.setOnline(); } resolver("onConnect"); super .send(new PayloadEnvelope()) .catch((e) => console.log(`ERROR VortexClientCapacitorWebsocket onConnect Send: ${e}`)); } onClose(event, rejector) { // onClose can fire from both the receive callback and the error // callback for the same close event; make it idempotent. if (!this.hasActiveConnection) { return; } this.hasActiveConnection = false; this.lastWebsocketId = null; this.vortexStatusService.logConnectionInfo(`WebSocket closed`); this.vortexStatusService.logDebug(`VortexClientCapacitorWebsocket.onClose close Event,` + ` ${JSON.stringify(event)}`); rejector("websocket closed", "close"); this.vortexStatusService.logInfo("VortexClientCapacitorWebsocket.onClose" + " calling setClosed"); this.setClosed(); this.vortexStatusService.logInfo("VortexClientCapacitorWebsocket.onClose" + " emitting websocketCloseCompleteSubject"); this.websocketCloseCompleteSubject.next(); } onError(event, exceptionStr, rejector) { const reject = () => { rejector(exceptionStr, event["callbackMethod"] || "onError"); }; exceptionStr = exceptionStr.toLowerCase(); // TODO, Add if (exceptionStr.includes("network") && exceptionStr.includes("down")) { this.vortexStatusService.setVortexState(VortexStateEnum.NoNetwork); reject(); return; } this.vortexStatusService.logDebug(`VortexClientCapacitorWebsocket.createSocket` + ` error ${exceptionStr} Event: ${JSON.stringify(event)}`); CordovaWebsocketPlugin.wsClose(this.lastWebsocketId, 1000, "error handler"); if (this.isShutdown) { reject(); return; } // Check if the server is still up, if it is than we've been logged out this.testIfOnlineAndLoggedOut() // .finally(() => { reject(); }); } async testIfOnlineAndLoggedOut() { if (!this._url.toLowerCase().startsWith("ws") || this._url.toLowerCase().startsWith("capacitor") || this._url.toLowerCase().indexOf("localhost") !== -1) { this.vortexStatusService.logDebug(`Vortex URL not initialised, it's |${this._url}|`); return; } const httpUrl = "http" + this._url.substring(2); console.log(`Websocket URL is |${this._url}|`); console.log(`Testing to |${httpUrl}|`); const headerDict = {}; for (const headerKey of this.headers.keys()) { headerDict[headerKey] = this.headers.get(headerKey); } const options = { url: httpUrl, headers: headerDict, readTimeout: 5000, connectTimeout: 2000, }; try { const response = await Http.get(options); this.vortexStatusService.logDebug(`VortexClientCapacitorWebsocket.testIfOnlineAndLoggedOut` + ` STATUS: = ${response.status}`); for (const key of Object.keys(response.headers)) { this.vortexStatusService.logDebug(`VortexClientCapacitorWebsocket.testIfOnlineAndLoggedOut` + ` HEADER: ${key} = ${response.headers[key]}`); } } catch (e) { this.vortexStatusService.logDebug(`VortexClientCapacitorWebsocket.testIfOnlineAndLoggedOut` + ` Error: = ${e}`); this.vortexStatusService.setVortexState(VortexStateEnum.NoNetwork); return; } this.vortexStatusService.setVortexState(VortexStateEnum.NetworkOnlineNoWebsocketResource); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVm9ydGV4Q2xpZW50Q2FwYWNpdG9yV2Vic29ja2V0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3ZvcnRleC9Wb3J0ZXhDbGllbnRDYXBhY2l0b3JXZWJzb2NrZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGVBQWUsRUFBRSxrQkFBa0IsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ3hFLE9BQU8sRUFBRSxlQUFlLEVBQXVCLE1BQU0sdUJBQXVCLENBQUM7QUFDN0UsT0FBTyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDakQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ3BELE9BQU8sRUFBRSxJQUFJLEVBQTZCLE1BQU0sMkJBQTJCLENBQUM7QUFHNUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUkvQiwrREFBK0Q7QUFDL0QsS0FBSyxVQUFVLGNBQWMsQ0FBQyxPQUFPO0lBQ2pDLHVEQUF1RDtJQUN2RCwrQ0FBK0M7SUFDL0MseUNBQXlDO0lBRXpDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDbkMsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQztZQUNuQyxJQUFJLENBQUMsS0FBSztnQkFDTixPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7Z0JBQ2YsWUFBWSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQy9CLENBQUM7WUFDRCxLQUFLLENBQUMsR0FBRztnQkFDTCxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDaEIsQ0FBQztTQUNKLENBQUMsQ0FBQztJQUNQLENBQUMsQ0FBQyxDQUFDO0FBQ1AsQ0FBQztBQUVELE1BQU0sT0FBTyw4QkFBK0IsU0FBUSxlQUFlO0lBQ3ZELGlCQUFpQixHQUFXLElBQUksQ0FBQyxLQUFLLENBQUMsYUFBYSxD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LEVBQUUsRUFBRTtZQUN6QyxVQUFVLENBQUMsU0FBUyxDQUFDLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUM7U0FDdkQ7UUFFRCxNQUFNLE9BQU8sR0FBZ0I7WUFDekIsR0FBRyxFQUFFLE9BQU87WUFDWixPQUFPLEVBQUUsVUFBVTtZQUNuQixXQUFXLEVBQUUsSUFBSTtZQUNqQixjQUFjLEVBQUUsSUFBSTtTQUN2QixDQUFDO1FBRUYsSUFBSTtZQUNBLE1BQU0sUUFBUSxHQUFpQixNQUFNLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDdkQsSUFBSSxDQUFDLG1CQUFtQixDQUFDLFFBQVEsQ0FDN0IseURBQXlEO2dCQUNyRCxjQUFjLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FDdEMsQ0FBQztZQUNGLEtBQUssTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQzdDLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxRQUFRLENBQzdCLHlEQUF5RDtvQkFDckQsWUFBWSxHQUFHLE1BQU0sUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUNuRCxDQUFDO2FBQ0w7U0FDSjtRQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ1IsSUFBSSxDQUFDLG1CQUFtQixDQUFDLFFBQVEsQ0FDN0IseURBQXlEO2dCQUNyRCxhQUFhLENBQUMsRUFBRSxDQUN2QixDQUFDO1lBQ0YsSUFBSSxDQUFDLG1CQUFtQixDQUFDLGNBQWMsQ0FBQyxlQUFlLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDbkUsT0FBTztTQUNWO1FBRUQsSUFBSSxDQUFDLG1CQUFtQixDQUFDLGNBQWMsQ0FDbkMsZUFBZSxDQUFDLGdDQUFnQyxDQUNuRCxDQUFDO0lBQ04sQ0FBQztDQUNKIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgVm9ydGV4Q2xpZW50QUJDLCBWb3J0ZXhDbGllbnRTdGF0ZUUgfSBmcm9tIFwiLi9Wb3J0ZXhDbGllbnRBQkNcIjtcbmltcG9ydCB7IFZvcnRleFN0YXRlRW51bSwgVm9ydGV4U3RhdHVzU2VydmljZSB9IGZyb20gXCIuL1ZvcnRleFN0YXR1c1NlcnZpY2VcIjtcbmltcG9ydCB7IGRhdGVTdHIsIGdldEZpbHRTdHIgfSBmcm9tIFwiLi9VdGlsTWlzY1wiO1xuaW1wb3J0IHsgUGF5bG9hZEVudmVsb3BlIH0gZnJvbSBcIi4vUGF5bG9hZEVudmVsb3BlXCI7XG5pbXBvcnQgeyBIdHRwLCBIdHRwT3B0aW9ucywgSHR0cFJlc3BvbnNlIH0gZnJvbSBcIkBjYXBhY2l0b3ItY29tbXVuaXR5L2h0dHBcIjtcbmltcG9ydCB7IEh0dHBIZWFkZXJzIH0gZnJvbSBcIkBhbmd1bGFyL2NvbW1vbi9odHRwXCI7XG5cbmltcG9ydCB7IFN1YmplY3QgfSBmcm9tIFwicnhqc1wiO1xuXG5kZWNsYXJlIGNvbnN0IENvcmRvdmFXZWJzb2NrZXRQbHVnaW46IGFueTtcblxuLy8gRGVsZXRlIHRoaXMgYWZ0ZXIgdXBncmFkaW5nIHRvIGEgbmV3ZXIgUlhKUyB3aXRoIHRoaXMgbWV0aG9kXG5hc3luYyBmdW5jdGlvbiBmaXJzdFZhbHVlRnJvbShzb3VyY2UkKSB7XG4gICAgLy8gVGhpcyBpcyBhIHNpbXBsaWZpZWQgdmVyc2lvbiB0aGF0IG1pZ2h0IG5vdCBoYXZlIGFsbFxuICAgIC8vIHRoZSBzdWJ0bGUgZGV0YWlscyBvZiB0aGUgZnVsbCBSeEpTIHZlcnNpb24sXG4gICAgLy8gYnV0IGF0IGl0cyBjb3JlIHRoaXMgaXMgdGhlIHByaW5jaXBsZS5cblxuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGNvbnN0IHN1YnNjcmlwdGlvbiA9IHNvdXJjZSQuc3Vic2NyaWJlKHtcbiAgICAgICAgICAgIG5leHQodmFsdWUpIHtcbiAgICAgICAgICAgICAgICByZXNvbHZlKHZhbHVlKTtcbiAgICAgICAgICAgICAgICBzdWJzY3JpcHRpb24udW5zdWJzY3JpYmUoKTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBlcnJvcihlcnIpIHtcbiAgICAgICAgICAgICAgICByZWplY3QoZXJyKTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuICAgIH0pO1xufVxuXG5leHBvcnQgY2xhc3MgVm9ydGV4Q2xpZW50Q2FwYWNpdG9yV2Vic29ja2V0IGV4dGVuZHMgVm9ydGV4Q2xpZW50QUJDIHtcbiAgICBwcml2YXRlIGxhc3RSZWNvbm5lY3REYXRlOiBudW1iZXIgPSBEYXRlLnBhcnNlKFwiMDEtSmFuLTIwMTdcIik7XG4gICAgcHJpdmF0ZSBsYXN0V2Vic29ja2V0SWQgPSBudWxsO1xuXG4gICAgcHJpdmF0ZSB1bnNlbnRCdWZmZXI6IHN0cmluZ1tdID0gW107XG5cbiAgICAvLyBNYWtlIG5vdGUgb2Ygd2hlbiB3ZSdyZSByZWNvbm5lY3RpbmcsXG4gICAgLy8gb3RoZXJ3aXNlIGNsb3NpbmcgdGhlIGxhc3Qgd2Vic29ja2V0IGNhdXNlcyBhIHJlY29ubmVjdGlvblxuICAgIC8vIGNhbGwgd2hpY2ggdGhlbiBjbG9zZXMgdGhlIGN1cnJlbnQgc29ja2V0IGJlaW5nIG9wZW4gKGluICBhIGxvb3ApXG4gICAgcHJpdmF0ZSByZWNvbm5lY3RpbmdJblByb2dyZXNzOiBib29sZWFuID0gZmFsc2U7XG5cbiAgICBwcml2YXRlIF9kYXRhOiBzdHJpbmcgPSBcIlwiO1xuICAgIHByaXZhdGUgX3ZvcnRleE1zZ3NRdWV1ZTogc3RyaW5nW10gPSBbXTtcblxuICAgIC8vIFRydWUgZnJvbSB0aGUgbW9tZW50IHdlIGNhbGwgd3NDb25uZWN0IHVudGlsIG9uQ2xvc2UgZmlyZXMuIExldHNcbiAgICAvLyBjbG9zZVdlYnNvY2tldCBhd2FpdCB0ZWFyLWRvd24gb2YgYSBzb2NrZXQgdGhhdCdzIHN0aWxsIG1pZC1jb25uZWN0XG4gICAgLy8gKGJlZm9yZSBsYXN0V2Vic29ja2V0SWQgaXMgYXNzaWduZWQpLCBub3QganVzdCBvbmUgdGhhdCdzIG9ubGluZS5cbiAgICBwcml2YXRlIGhhc0FjdGl2ZUNvbm5lY3Rpb246IGJvb2xlYW4gPSBmYWxzZTtcblxuICAgIHByaXZhdGUgd2Vic29ja2V0Q2xvc2VDb21wbGV0ZVN1YmplY3QgPSBuZXcgU3ViamVjdDx2b2lkPigpO1xuXG4gICAgY29uc3RydWN0b3IoXG4gICAgICAgIHZvcnRleFN0YXR1c1NlcnZpY2U6IFZvcnRleFN0YXR1c1NlcnZpY2UsXG4gICAgICAgIHVybDogc3RyaW5nLFxuICAgICAgICB2b3J0ZXhDbGllbnROYW1lOiBzdHJpbmcsXG4gICAgICAgIGhlYWRlcnM6IEh0dHBIZWFkZXJzLFxuICAgICkge1xuICAgICAgICBzdXBlcih2b3J0ZXhTdGF0dXNTZXJ2aWNlLCB1cmwsIHZvcnRleENsaWVudE5hbWUsIGhlYWRlcnMpO1xuICAgIH1cblxuICAgIGdldCBpc1JlYWR5KCk6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gdGhpcy5pc09ubGluZTtcbiAgICB9XG5cbiAgICAvLyBPVkVSUklERSBTZW5kXG4gICAgc2VuZChwYXlsb2FkRW52ZWxvcGU6IFBheWxvYWRFbnZlbG9wZSB8IFBheWxvYWRFbnZlbG9wZVtdKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGlmICghdGhpcy5pc1JlYWR5KSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJXZWJzb2NrZXQgdm9ydGV4IGlzIG5vdCBvbmxpbmUuXCIpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHN1cGVyLnNlbmQocGF5bG9hZEVudmVsb3BlKTtcbiAgICB9XG5cbiAgICAvLyBPVkVSUklERSByZWNvbm5lY3RcbiAgICBhc3luYyByZWNvbm5lY3QoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGlmICh0aGlzLnVybCA9PSBudWxsKSB7XG4gICAgICAgICAgICB0aGlzLnZvcnRleFN0YXR1c1NlcnZpY2UubG9nRGVidWcoXG4gICAgICAgICAgICAgICAgXCJWb3J0ZXhDbGllbnRDYXBhY2l0b3JXZWJzb2NrZXQucmVjb25uZWN0IHRoaXMudXJsIGlzIG51bGwsIHJldHVybmluZ1wiLFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIDUwMDApKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMudm9ydGV4U3RhdHVzU2VydmljZS5sb2dEZWJ1ZyhcbiAgICAgICAgICAgIFwiVm9ydGV4Q2xpZW50Q2FwYWNpdG9yV2Vic29ja2V0LnJlY29ubmVjdCBjYWxsZWRcIixcbiAgICAgICAgKTtcbiAgICAgICAgaWYgKHRoaXMucmVjb25uZWN0aW5nSW5Qcm9ncmVzcyA9PT0gdHJ1ZSkge1xuICAgICAgICAgICAgdGhpcy52b3J0ZXhTdGF0dXNTZXJ2aWNlLmxvZ0RlYnVnKFxuICAgICAgICAgICAgICAgIFwiVm9ydGV4Q2xpZW50Q2FwYWNpdG9yV2Vic29ja2V0LnJlY29ubmVjdCByZXR1cm5pbmcgIzFcIixcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5jbGVhckJlYXRUaW1lcigpO1xuICAgICAgICB0aGlzLl92b3J0ZXhTdGF0ZSA9IFZvcnRleENsaWVudFN0YXRlRS5JZGxlO1xuICAgICAgICB0aGlzLnJlY29ubmVjdGluZ0luUHJvZ3Jlc3MgPSB0cnVlO1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgLy8gV2UgYXdhaXQgdGhpcywgdG8gZW5zdXJlIHdlIHdhaXQgZm9yIHRoZSBsYXN0XG4gICAgICAgICAgICAvLyBjb25uZWN0aW5nIGJlZm9yZSByZXRyeWluZy5cbiAgICAgICAgICAgIGF3YWl0IHRoaXMuY3JlYXRlU29ja2V0KCk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHRoaXMudm9ydGV4U3RhdHVzU2VydmljZS5sb2dEZWJ1ZyhcbiAgICAgICAgICAgICAgICBcIlZvcnRleENsaWVudENhcGFjaXRvcldlYnNvY2tldC5yZWNvbm5lY3RcIiArXG4gICAgICAgICAgICAgICAgICAgIGAgRmFpbGVkIHRvIGNvbm5lY3Qgd2Vic29ja2V0ICR7ZX1gLFxuICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLnJlY29ubmVjdGluZ0luUHJvZ3Jlc3MgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5yZXN0YXJ0VGltZXIoKTtcblxuICAgICAgICB0aGlzLnZvcnRleFN0YXR1c1NlcnZpY2UubG9nRGVidWcoXG4gICAgICAgICAgICBcIlZvcnRleENsaWVudENhcGFjaXRvcldlYnNvY2tldC5yZWNvbm5lY3QgcmV0dXJuaW5nICMyXCIsXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcHJvdGVjdGVkIHNlbmRWb3J0ZXhNc2codm9ydGV4TXNnczogc3RyaW5nW10pOiB2b2lkIHtcbiAgICAgICAgdGhpcy51bnNlbnRCdWZmZXIuYWRkKHZvcnRleE1zZ3MpO1xuXG4gICAgICAgIHRoaXMuc2VuZE1lc3NhZ2VzKCk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBzZW5kTWVzc2FnZXMoKSB7XG4gICAgICAgIHdoaWxlICh0aGlzLnVuc2VudEJ1ZmZlci5sZW5ndGggIT09IDApIHtcbiAgICAgICAgICAgIGlmICghdGhpcy5pc1JlYWR5KSByZXR1cm47XG5cbiAgICAgICAgICAgIGNvbnN0IHZvcnRleE1zZyA9IHRoaXMudW5zZW50QnVmZmVyLnNoaWZ0KCk7XG4gICAgICAgICAgICBDb3Jkb3ZhV2Vic29ja2V0UGx1Z2luLndzU2VuZCh0aGlzLmxhc3RXZWJzb2NrZXRJZCwgdm9ydGV4TXNnICsgXCIuXCIpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBfcHJvY2Vzc0RhdGEoKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzLl9kYXRhKSB7XG4gICAgICAgICAgICBsZXQgaW5kZXhPZkRvdDogbnVtYmVyID0gdGhpcy5fZGF0YS5pbmRleE9mKFwiLlwiKTtcbiAgICAgICAgICAgIHdoaWxlIChpbmRleE9mRG90ICE9PSAtMSkge1xuICAgICAgICAgICAgICAgIGNvbnN0IHZvcnRleE1zZyA9IHRoaXMuX2RhdGEuc2xpY2UoMCwgaW5kZXhPZkRvdCk7XG4gICAgICAgICAgICAgICAgdGhpcy5fZGF0YSA9IHRoaXMuX2RhdGEuc2xpY2UoaW5kZXhPZkRvdCArIDEpO1xuXG4gICAgICAgICAgICAgICAgaWYgKHZvcnRleE1zZy5sZW5ndGggIT09IDApIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5fdm9ydGV4TXNnc1F1ZXVlLnB1c2godm9ydGV4TXNnKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpbmRleE9mRG90ID0gdGhpcy5fZGF0YS5pbmRleE9mKFwiLlwiKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHdoaWxlICh0aGlzLl92b3J0ZXhNc2dzUXVldWUubGVuZ3RoICE9PSAwKSB7XG4gICAgICAgICAgICB0aGlzLl9wcm9jZXNzVm9ydGV4TXNncygpIC8vXG4gICAgICAgICAgICAgICAgLmNhdGNoKChlKSA9PlxuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZyhgRVJST1IgVm9ydGV4Q2xpZW50Q2FwYWNpdG9yV2Vic29ja2V0OiAke2V9YCksXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHByaXZhdGUgYXN5bmMgX3Byb2Nlc3NWb3J0ZXhNc2dzKCkge1xuICAgICAgICB3aGlsZSAodGhpcy5fdm9ydGV4TXNnc1F1ZXVlLmxlbmd0aCAhPT0gMCkge1xuICAgICAgICAgICAgY29uc3Qgdm9ydGV4TXNnID0gdGhpcy5fdm9ydGV4TXNnc1F1ZXVlLnNoaWZ0KCk7XG5cbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgY29uc3QgcGF5bG9hZEVudmVsb3BlID1cbiAgICAgICAgICAgICAgICAgICAgYXdhaXQgUGF5bG9hZEVudmVsb3BlLmZyb21Wb3J0ZXhNc2codm9ydGV4TXNnKTtcblxuICAgICAgICAgICAgICAgIHRoaXMuX2RlbGl2ZXJQYXlsb2FkKHBheWxvYWRFbnZlbG9wZSk7XG4gICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAgICAgY29uc29sZS5sb2coXG4gICAgICAgICAgICAgICAgICAgIGRhdGVTdHIoKSArXG4gICAgICAgICAgICAgICAgICAgICAgICBgRVJST1I6IHByb2Nlc3Npbmcgdm9ydGV4TXNnXFxuJHtlfVxcbiR7dm9ydGV4TXNnfWAsXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIHByaXZhdGUgX2RlbGl2ZXJQYXlsb2FkKHBheWxvYWRFbnZlbG9wZTogUGF5bG9hZEVudmVsb3BlKSB7XG4gICAgICAgIHRoaXMucmVjZWl2ZShwYXlsb2FkRW52ZWxvcGUpO1xuICAgIH1cblxuICAgIHByb3RlY3RlZCBhc3luYyBzaHV0ZG93bigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgdGhpcy5zZXRTaHV0ZG93bigpO1xuICAgICAgICB0aGlzLnZvcnRleFN0YXR1c1NlcnZpY2UubG9nRGVidWcoXG4gICAgICAgICAgICBcIlZvcnRleENsaWVudENhcGFjaXRvcldlYnNvY2tldC5zaHV0ZG93biBjYWxsZWRcIixcbiAgICAgICAgKTtcbiAgICAgICAgYXdhaXQgdGhpcy5jbG9zZVdlYnNvY2tldCgpO1xuICAgICAgICB0aGlzLnZvcnRleFN0YXR1c1NlcnZpY2UubG9nRGVidWcoXG4gICAgICAgICAgICBcIlZvcnRleENsaWVudENhcGFjaXRvcldlYnNvY2tldC5zaHV0ZG93biByZXR1cm5lZFwiLFxuICAgICAgICApO1xuICAgIH1cblxuICAgIHByaXZhdGUgYXN5bmMgY2xvc2VXZWJzb2NrZXQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHRoaXMudm9ydGV4U3RhdHVzU2VydmljZS5sb2dEZWJ1ZyhcbiAgICAgICAgICAgIFwiVm9ydGV4Q2xpZW50Q2FwYWNpdG9yV2Vic29ja2V0LmNsb3NlV2Vic29ja2V0IGNhbGxlZFwiICtcbiAgICAgICAgICAgICAgICBgIF92b3J0ZXhTdGF0ZT0ke3RoaXMuX3ZvcnRleFN0YXRlfWAsXG4gICAgICAgICk7XG5cbiAgICAgICAgaWYgKHRoaXMuaGFzQWN0aXZlQ29ubmVjdGlvbikge1xuICAgICAgICAgICAgdGhpcy52b3J0ZXhTdGF0dXNTZXJ2aWNlLmxvZ0RlYnVnKFwiV2Vic29ja2V0IGNsb3NlIHN0YXJ0aW5nXCIpO1xuICAgICAgICAgICAgdGhpcy5zZXRDbG9zaW5nKCk7XG4gICAgICAgICAgICAvLyBJZiB0aGUgcGx1Z2luIGdhdmUgdXMgYW4gaWQsIGFzayBpdCB0byBjbG9zZS4gSWYgbm90XG4gICAgICAgICAgICAvLyAoc3RpbGwgbWlkLWNvbm5lY3QpLCB3ZSBjYW4gb25seSB3YWl0IGZvciB0aGUgcGx1Z2luJ3NcbiAgICAgICAgICAgIC8vIG93biB0aW1lb3V0IHRvIGZpcmUgb25DbG9zZS5cbiAgICAgICAgICAgIGlmICh0aGlzLmxhc3RXZWJzb2NrZXRJZCAhPSBudWxsKSB7XG4gICAgICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICAgICAgLy8gU29ja2V0Um9ja2V0IGFzc2VydHMgKGFib3J0cyB0aGUgcHJvY2Vzcykgb24gaW52YWxpZFxuICAgICAgICAgICAgICAgICAgICAvLyBjbG9zZSBjb2Rlcy4gQWx3YXlzIHBhc3MgMTAwMCAobm9ybWFsIGNsb3NlKSBhbmQgYVxuICAgICAgICAgICAgICAgICAgICAvLyByZWFzb24gc3RyaW5nIHNvIGl0J3MgbmV2ZXIgbmlsLzAgb24gdGhlIG5hdGl2ZSBzaWRlLlxuICAgICAgICAgICAgICAgICAgICBDb3Jkb3ZhV2Vic29ja2V0UGx1Z2luLndzQ2xvc2UoXG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmxhc3RXZWJzb2NrZXRJZCxcbiAgICAgICAgICAgICAgICAgICAgICAgIDEwMDAsXG4gICAgICAgICAgICAgICAgICAgICAgICBcImNsaWVudCBjbG9zaW5nXCJcbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIHBhc3NcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0aGlzLnZvcnRleFN0YXR1c1NlcnZpY2UubG9nRGVidWcoXG4gICAgICAgICAgICAgICAgXCJWb3J0ZXhDbGllbnRDYXBhY2l0b3JXZWJzb2NrZXQuY2xvc2VXZWJzb2NrZXQgd2FpdGluZyBmb3IgY29uZmlybWF0aW9uXCIsXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgY29uc3QgY2xvc2VkID0gYXdhaXQgUHJvbWlzZS5yYWNlKFtcbiAgICAgICAgICAgICAgICBmaXJzdFZhbHVlRnJvbSh0aGlzLndlYnNvY2tldENsb3NlQ29tcGxldGVTdWJqZWN0KS50aGVuKFxuICAgICAgICAgICAgICAgICAgICAoKSA9PiB0cnVlLFxuICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgbmV3IFByb21pc2U8Ym9vbGVhbj4oKHJlc29sdmUpID0+XG4gICAgICAgICAgICAgICAgICAgIHNldFRpbWVvdXQoKCkgPT4gcmVzb2x2ZShmYWxzZSksIDUwMDApLFxuICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICBdKTtcbiAgICAgICAgICAgIGlmICghY2xvc2VkKSB7XG4gICAgICAgICAgICAgICAgLy8gVGhlIGNsb3NlIGNhbGxiYWNrIG5ldmVyIGZpcmVkIChlLmcuIHNvY2tldCBhbHJlYWR5IGRlYWRcbiAgICAgICAgICAgICAgICAvLyB3aXRoIGNvZGUgMTAwNiBiZWZvcmUgd2UgY2FsbGVkIHdzQ2xvc2UpLiBGb3JjZSBjbGVhbnVwLlxuICAgICAgICAgICAgICAgIHRoaXMudm9ydGV4U3RhdHVzU2VydmljZS5sb2dEZWJ1ZyhcbiAgICAgICAgICAgICAgICAgICAgXCJWb3J0ZXhDbGllbnRDYXBhY2l0b3JXZWJzb2NrZXQuY2xvc2VXZWJzb2NrZXQgdGltZWQgb3V0XCIgK1xuICAgICAgICAgICAgICAgICAgICAgICAgXCIgd2FpdGluZyBmb3IgY2xvc2Ug4oCUIGZvcmNpbmcgY2xlYW51cFwiLFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgdGhpcy5oYXNBY3RpdmVDb25uZWN0aW9uID0gZmFsc2U7XG4gICAgICAgICAgICAgICAgdGhpcy5sYXN0V2Vic29ja2V0SWQgPSBudWxsO1xuICAgICAgICAgICAgICAgIHRoaXMuc2V0Q2xvc2VkKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aGlzLnNldENsb3NlZCgpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy52b3J0ZXhTdGF0dXNTZXJ2aWNlLmxvZ0RlYnVnKFxuICAgICAgICAgICAgXCJWb3J0ZXhDbGllbnRDYXBhY2l0b3JXZWJzb2NrZXQuY2xvc2VXZWJzb2NrZXQgcmV0dXJuaW5nXCIsXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBhc3luYyBjcmVhdGVTb2NrZXQoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIHRoaXMudm9ydGV4U3RhdHVzU2VydmljZS5sb2dEZWJ1ZyhcbiAgICAgICAgICAgIFwiVm9ydGV4Q2xpZW50Q2FwYWNpdG9yV2Vic29ja2V0LmNyZWF0ZVNvY2tldCBjYWxsZWRcIixcbiAgICAgICAgKTtcbiAgICAgICAgLy8gSWYgd2UncmUgYWxyZWFkeSBjb25uZWN0aW5nLCB0aGVuIGRvIG5vdGhpbmdcbiAgICAgICAgaWYgKHRoaXMuaXNDb25uZWN0aW5nKSB7XG4gICAgICAgICAgICBpZiAoIXRoaXMuaXNTaHV0ZG93bikge1xuICAgICAgICAgICAgICAgIHRoaXMudm9ydGV4U3RhdHVzU2VydmljZS5sb2dEZWJ1ZyhcbiAgICAgICAgICAgICAgICAgICAgXCJWb3J0ZXhDbGllbnRDYXBhY2l0b3JXZWJzb2NrZXQuY3JlYXRlU29ja2V0IHJldHVybmluZyAjMVwiLFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmxvZyhcbiAgICAgICAgICAgICAgICAgICAgXCJBYm9ydGluZyBXZWJTb2NrZXQgY29ubmVjdGlvbiBhdHRlbXB0LFwiICtcbiAgICAgICAgICAgICAgICAgICAgICAgIFwiIHRoaXMgaXMgcHJvYmFibHkgYmVjYXVzZSBvZiBWb3J0ZXggcmVjb25uZWN0aW9uXCIsXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICBDb3Jkb3ZhV2Vic29ja2V0UGx1Z2luLndzQ2xvc2UoXG4gICAgICAgICAgICAgICAgICAgIHRoaXMubGFzdFdlYnNvY2tldElkLFxuICAgICAgICAgICAgICAgICAgICAxMDAwLFxuICAgICAgICAgICAgICAgICAgICBcImFib3J0aW5nIHJlY29ubmVjdFwiXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgICAgICAvLyBwYXNzXG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnZvcnRleFN0YXR1c1NlcnZpY2UubG9nRGVidWcoXG4gICAgICAgICAgICBcIlZvcnRleENsaWVudENhcGFjaXRvcldlYnNvY2tldC5jcmVhdGVTb2NrZXQgQ2xvc2luZyBvbGQgd2Vic29ja2V0XCIsXG4gICAgICAgICk7XG4gICAgICAgIGF3YWl0IHRoaXMuY2xvc2VXZWJzb2NrZXQoKTtcblxuICAgICAgICAvLyBEb24ndCBjb250aW51YWxseSByZWNvbm5lY3RcbiAgICAgICAgbGV0IHJlY29ubmVjdERpZmZNcyA9IERhdGUubm93KCkgLSB0aGlzLmxhc3RSZWNvbm5lY3REYXRlO1xuICAgICAgICBpZiAocmVjb25uZWN0RGlmZk1zIDwgdGhpcy5SRUNPTk5FQ1RfQkFDS09GRl9TRUNPTkRTICogMTAwMCkge1xuICAgICAgICAgICAgdGhpcy52b3J0ZXhTdGF0dXNTZXJ2aWNlLmxvZ0RlYnVnKFxuICAgICAgICAgICAgICAgIFwiVm9ydGV4Q2xpZW50Q2FwYWNpdG9yV2Vic29ja2V0LmNyZWF0ZVNvY2tldCByZXR1cm5pbmcgIzIgLSBiYWNrb2ZmXCIgK1xuICAgICAgICAgICAgICAgICAgICBcIiB0aW1lb3V0XCIsXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgc2V0VGltZW91dChcbiAgICAgICAgICAgICAgICAoKSA9PiB0aGlzLmNyZWF0ZVNvY2tldCgpLFxuICAgICAgICAgICAgICAgIHRoaXMuUkVDT05ORUNUX0JBQ0tPRkZfU0VDT05EUyAqIDEwMDAgLSByZWNvbm5lY3REaWZmTXMgKyAxMCxcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLmxhc3RSZWNvbm5lY3REYXRlID0gRGF0ZS5ub3coKTtcblxuICAgICAgICAvLyBQcmVwYXJlIHRoZSBhcmdzIHRvIHNlbmRcbiAgICAgICAgY29uc3QgYXJncyA9IHtcbiAgICAgICAgICAgIHZvcnRleFV1aWQ6IHRoaXMudXVpZCxcbiAgICAgICAgICAgIHZvcnRleE5hbWU6IHRoaXMubmFtZSxcbiAgICAgICAgfTtcblxuICAgICAgICB0aGlzLnZvcnRleFN0YXR1c1NlcnZpY2UubG9nQ29ubmVjdGlvbkluZm8oXG4gICAgICAgICAgICBgJHtkYXRlU3RyKCl9IFdlYlNvY2tldCwgY29ubmVjdGluZyB0byAke3RoaXMudXJsfWAsXG4gICAgICAgICk7XG5cbiAgICAgICAgY29uc3QgaGVhZGVyc0RpY3QgPSB7fTtcbiAgICAgICAgZm9yIChjb25zdCBrZXkgb2YgdGhpcy5oZWFkZXJzLmtleXMoKSkge1xuICAgICAgICAgICAgaGVhZGVyc0RpY3Rba2V5XSA9IHRoaXMuaGVhZGVycy5nZXQoa2V5KTtcbiAgICAgICAgfVxuICAgICAgICBjb25zb2xlLmxvZyhcbiAgICAgICAgICAgIFwiUmVjb25uZWN0aW5nIHdpdGggaGVhZGVycyBcIiArIE9iamVjdC5rZXlzKGhlYWRlcnNEaWN0KS5qb2luKFwiLCBcIiksXG4gICAgICAgICk7XG5cbiAgICAgICAgYXdhaXQgbmV3IFByb21pc2U8dm9pZD4oKF9yZXNvbHZlLCBfcmVqZWN0KSA9PiB7XG4gICAgICAgICAgICBsZXQgcHJvbWlzZUNhbGxlZCA9IGZhbHNlO1xuICAgICAgICAgICAgY29uc3QgcmVzb2x2ZXIgPSAoZXZlbnQ6IHN0cmluZykgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChwcm9taXNlQ2FsbGVkKSByZXR1cm47XG4gICAgICAgICAgICAgICAgcHJvbWlzZUNhbGxlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgdGhpcy52b3J0ZXhTdGF0dXNTZXJ2aWNlLmxvZ0RlYnVnKFxuICAgICAgICAgICAgICAgICAgICBcIlZvcnRleENsaWVudENhcGFjaXRvcldlYnNvY2tldC5jcmVhdGVTb2NrZXQgY2FsbGluZyByZXNvbHZlXCIgK1xuICAgICAgICAgICAgICAgICAgICAgICAgXCIgZnJvbSBcIiArXG4gICAgICAgICAgICAgICAgICAgICAgICBldmVudCxcbiAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgIF9yZXNvbHZlKCk7XG4gICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICBjb25zdCByZWplY3RvciA9IChlcnJvcjogc3RyaW5nLCBldmVudDogc3RyaW5nKSA9PiB7XG4gICAgICAgICAgICAgICAgaWYgKHByb21pc2VDYWxsZWQpIHJldHVybjtcbiAgICAgICAgICAgICAgICBwcm9taXNlQ2FsbGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB0aGlzLnZvcnRleFN0YXR1c1NlcnZpY2UubG9nRGVidWcoXG4gICAgICAgICAgICAgICAgICAgIFwiVm9ydGV4Q2xpZW50Q2FwYWNpdG9yV2Vic29ja2V0LmNyZWF0ZVNvY2tldCBjYWxsaW5nIHJlamVjdFwiICtcbiAgICAgICAgICAgICAgICAgICAgICAgIFwiIGZyb20gJ1wiICtcbiAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50ICtcbiAgICAgICAgICAgICAgICAgICAgICAgIFwiJyB3aXRoIG1lc3NhZ2U6IFwiICtcbiAgICAgICAgICAgICAgICAgICAgICAgIGVycm9yLFxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgX3JlamVjdChlcnJvcik7XG4gICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICB0aGlzLnZvcnRleFN0YXR1c1NlcnZpY2UubG9nRGVidWcoXG4gICAgICAgICAgICAgICAgXCJWb3J0ZXhDbGllbnRDYXBhY2l0b3JXZWJzb2NrZXQuY3JlYXRlU29ja2V0IENyZWF0aW5nIHNvY2tldFwiLFxuICAgICAgICAgICAgKTtcblxuICAgICAgICAgICAgY29uc3Qgd3NPcHRpb25zID0ge1xuICAgICAgICAgICAgICAgIHVybDogdGhpcy51cmwgKyBnZXRGaWx0U3RyKGFyZ3MpLFxuICAgICAgICAgICAgICAgIHRpbWVvdXQ6IDEyMCAqIDEwMDAsXG4gICAgICAgICAgICAgICAgcGluZ0ludGVydmFsOiA2MCAqIDEwMDAsXG4gICAgICAgICAgICAgICAgaGVhZGVyczogaGVhZGVyc0RpY3QsXG4gICA