@synerty/vortexjs
Version:
Custom observable data serialisation and routing based on Angular 2+
389 lines • 60.7 kB
JavaScript
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