vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
510 lines (508 loc) • 69 kB
JavaScript
/**
* vite-plugin-react-server
* Copyright (c) Nico Brinkkemper
* MIT License
*/
import { createRenderMetrics } from '../metrics/createRenderMetrics.js';
import { routeToURL } from '../utils/routeToURL.js';
import { handleError } from '../error/handleError.js';
import { assertReactServer } from '../config/getCondition.js';
import { renderRscStream } from '../stream/renderRscStream.server.js';
import { createMainThreadHandlers } from '../stream/createMainThreadHandlers.js';
import { createRscToHtmlStream } from './rscToHtmlStream.server.js';
import { resolveComponent } from '../helpers/resolveComponent.js';
import { resolvePageAndProps } from '../helpers/resolvePageAndProps.js';
import { Root } from '../components/root.js';
import { Html } from '../components/html.js';
import { createStreamMetrics } from '../metrics/createStreamMetrics.js';
import { join } from 'node:path';
import { trackHeadlessStreamError, createHeadlessStreamState, hasHeadlessStreamError } from '../helpers/headlessStreamState.js';
const renderPage = async function* renderPage2(handlerOptions) {
assertReactServer();
const baseDir = join(
handlerOptions.build.outDir,
handlerOptions.build.static
);
const routePath = handlerOptions.route.replace(/^\//, "");
const htmlMetrics = createRenderMetrics({
route: handlerOptions.route,
type: "html",
fromMainThread: false,
// Server: HTML rendered in worker
fromRscWorker: false,
fromHtmlWorker: true,
baseDir,
routePath,
fileName: handlerOptions.build.htmlOutputPath,
outputPath: join(baseDir, routePath, handlerOptions.build.htmlOutputPath)
});
const rscFullMetrics = createRenderMetrics({
route: handlerOptions.route,
type: "rsc-full",
fromMainThread: true,
// Server: RSC rendered on main thread
fromRscWorker: false,
fromHtmlWorker: false
});
const rscHeadlessMetrics = createRenderMetrics({
route: handlerOptions.route,
type: "rsc-headless",
fromMainThread: true,
// Server: RSC rendered on main thread
fromRscWorker: false,
fromHtmlWorker: false,
baseDir,
routePath,
fileName: handlerOptions.build.rscOutputPath,
outputPath: join(baseDir, routePath, handlerOptions.build.rscOutputPath)
});
let headlessRscHandler = null;
let fullRscHandler = null;
let htmlTransformStream = null;
let headlessStreamErrored = false;
let headlessError = null;
let htmlStreamErrored = false;
let htmlStreamError = null;
const headlessStreamState = createHeadlessStreamState();
try {
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.server] Server-side rendering for route: ${handlerOptions.route}`
);
}
if (!handlerOptions.url) {
handlerOptions.url = routeToURL(
handlerOptions.route,
handlerOptions.moduleBaseURL,
handlerOptions.build.rscOutputPath
);
}
let PageComponent = null;
let RootComponent = null;
let HtmlComponent = null;
let pageProps = {};
if (handlerOptions.pagePath) {
try {
const pageAndPropsResult = await resolvePageAndProps({
pagePath: handlerOptions.pagePath,
pageExportName: handlerOptions.pageExportName,
propsPath: handlerOptions.propsPath,
propsExportName: handlerOptions.propsExportName,
loader: handlerOptions.loader,
verbose: handlerOptions.verbose,
logger: handlerOptions.logger,
route: handlerOptions.route,
url: handlerOptions.url,
moduleBaseURL: handlerOptions.moduleBaseURL,
build: {
rscOutputPath: handlerOptions.build.rscOutputPath
}
});
if (pageAndPropsResult.type === "success") {
PageComponent = pageAndPropsResult.PageComponent;
pageProps = pageAndPropsResult.pageProps || {};
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.server] Successfully loaded page and props for route ${handlerOptions.route}: pageProps=${JSON.stringify(pageProps)}`
);
}
} else {
handlerOptions.logger?.warn(
`Failed to load page and props from ${handlerOptions.pagePath}: ${pageAndPropsResult.error?.message || "Unknown error"}`
);
}
} catch (error) {
handlerOptions.logger?.warn(
`Error loading page and props from ${handlerOptions.pagePath}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
if (handlerOptions.rootPath) {
try {
const rootResult = await resolveComponent({
componentPath: handlerOptions.rootPath,
exportName: handlerOptions.rootExportName,
loader: handlerOptions.loader
});
if (rootResult.type === "success") {
RootComponent = rootResult.component;
} else {
handlerOptions.logger?.warn(
`Failed to load Root component from ${handlerOptions.rootPath}: ${rootResult.error?.message || "Unknown error"}`
);
}
} catch (error) {
handlerOptions.logger?.warn(
`Error loading Root component from ${handlerOptions.rootPath}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
if (handlerOptions.htmlPath) {
try {
const htmlResult = await resolveComponent({
componentPath: handlerOptions.htmlPath,
exportName: handlerOptions.htmlExportName,
loader: handlerOptions.loader
});
if (htmlResult.type === "success") {
HtmlComponent = htmlResult.component;
} else {
handlerOptions.logger?.warn(
`Failed to load Html component from ${handlerOptions.htmlPath}: ${htmlResult.error?.message || "Unknown error"}`
);
}
} catch (error) {
handlerOptions.logger?.warn(
`Error loading Html component from ${handlerOptions.htmlPath}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
if (!RootComponent) {
RootComponent = Root;
}
if (!HtmlComponent) {
HtmlComponent = Html;
}
if (!PageComponent || !RootComponent || !HtmlComponent) {
yield {
type: "error",
error: new Error(
`Component resolution failed: missing required components (Page: ${!!PageComponent}, Root: ${!!RootComponent}, Html: ${!!HtmlComponent})`
),
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
return;
}
const uniqueId = handlerOptions.id ?? `${handlerOptions.route}?id=${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
const newHandlerOptions = {
...handlerOptions,
id: uniqueId,
url: `${handlerOptions.url}`,
route: `${handlerOptions.route}`,
PageComponent,
RootComponent,
HtmlComponent,
pageProps
};
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.server] Created newHandlerOptions for route ${handlerOptions.route} with pageProps: ${JSON.stringify(pageProps)}`
);
}
const headlessHandlers = createMainThreadHandlers(
handlerOptions,
(error, isPanic) => {
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.server] Headless stream error handler called for route ${handlerOptions.route}: ${error.message}, isPanic: ${isPanic}`
);
}
headlessStreamErrored = true;
headlessError = error instanceof Error ? error : new Error("Headless RSC stream failed");
trackHeadlessStreamError(headlessStreamState, handlerOptions.route, headlessError);
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.server] Stored headless stream error for route ${handlerOptions.route} in headlessStreamErrors map`
);
}
if (isPanic) {
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.server] Panic error detected for route ${handlerOptions.route}, will be handled by renderPages`
);
}
}
}
);
headlessHandlers.onData = (_id, chunk) => {
rscHeadlessMetrics.chunks++;
rscHeadlessMetrics.streamMetrics.bytes += chunk.length;
};
headlessRscHandler = renderRscStream(
{
...newHandlerOptions,
htmlPath: "",
// Headless RSC - no HTML wrapper
// If we expect errors, provide a safe Page component that doesn't throw
PageComponent: newHandlerOptions.PageComponent
// Use original for now, will be overridden if errors occur
},
headlessHandlers
);
headlessRscHandler.rscStream.on("end", () => {
if (!hasHeadlessStreamError(headlessStreamState, handlerOptions.route)) {
headlessStreamState.elements.set(uniqueId, {
PageComponent: newHandlerOptions.PageComponent,
errored: false
});
if (handlerOptions.verbose) {
handlerOptions.logger?.info(`[renderPage.server] Stored PageComponent for headless stream ${uniqueId}`);
}
} else {
if (handlerOptions.verbose) {
handlerOptions.logger?.info(`[renderPage.server] Headless stream errored for route ${handlerOptions.route}, not storing PageComponent for reuse`);
}
}
});
let fullPanicError = null;
const fullHandlers = createMainThreadHandlers(
handlerOptions,
(error, isPanic) => {
if (isPanic) {
fullPanicError = error instanceof Error ? error : new Error("Full RSC stream failed");
}
}
);
fullHandlers.onData = (_id, chunk) => {
rscFullMetrics.chunks++;
rscFullMetrics.streamMetrics.bytes += chunk.length;
};
const hasExistingHeadlessError = hasHeadlessStreamError(headlessStreamState, handlerOptions.route);
const shouldUseFallback = headlessStreamErrored || hasExistingHeadlessError;
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.server] Creating full RSC handler options for route ${handlerOptions.route}: headlessStreamErrored=${headlessStreamErrored}, hasExistingHeadlessError=${hasExistingHeadlessError}, shouldUseFallback=${shouldUseFallback}`
);
}
const SafePageComponent = (props) => {
const hasError = hasHeadlessStreamError(headlessStreamState, handlerOptions.route);
if (hasError) {
return null;
}
return newHandlerOptions.PageComponent(props);
};
const fullRscHandlerOptions = {
...newHandlerOptions,
htmlPath: void 0,
// Full RSC - include HTML wrapper
headlessStreamElements: headlessStreamState.elements,
// Pass the storage map for reuse
// Use SafePageComponent that returns null when there are headless stream errors
PageComponent: SafePageComponent
};
headlessRscHandler.rscStream.on("end", () => {
if (!hasHeadlessStreamError(headlessStreamState, handlerOptions.route)) {
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.server] Headless stream completed successfully for route ${handlerOptions.route}`
);
}
}
});
if (handlerOptions.verbose) {
handlerOptions.logger?.info(
`[renderPage.server] Created PageComponent that uses React.use() to consume headless stream for route ${handlerOptions.route}`
);
}
fullRscHandler = renderRscStream(fullRscHandlerOptions, fullHandlers);
if (fullPanicError) {
yield {
type: "error",
error: fullPanicError,
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
return;
}
htmlTransformStream = createRscToHtmlStream({
id: handlerOptions.id,
worker: handlerOptions.worker,
route: handlerOptions.route,
url: handlerOptions.url,
moduleRootPath: handlerOptions.moduleRootPath,
moduleBasePath: handlerOptions.moduleBasePath,
moduleBaseURL: handlerOptions.moduleBaseURL,
projectRoot: handlerOptions.projectRoot,
build: handlerOptions.build,
panicThreshold: handlerOptions.panicThreshold,
verbose: handlerOptions.verbose,
signal: handlerOptions.signal,
logger: handlerOptions.logger,
htmlWorker: handlerOptions.htmlWorker,
clientPipeableStreamOptions: handlerOptions.clientPipeableStreamOptions,
onMetrics: handlerOptions.onMetrics,
htmlTimeout: handlerOptions.htmlTimeout || 15e3,
rscStream: fullRscHandler.rscStream,
onError: (error, isPanic) => {
htmlStreamErrored = true;
htmlStreamError = error;
if (isPanic) {
if (handlerOptions.verbose) {
handlerOptions.logger?.error(
`[renderPage.server] HTML stream panic error for route ${handlerOptions.route}: ${error.message}`
);
}
} else {
if (handlerOptions.verbose) {
handlerOptions.logger?.warn(
`[renderPage.server] HTML stream error for route ${handlerOptions.route}: ${error.message}`
);
}
}
}
});
const rscStreamWrapper = {
pipe: (destination) => {
const streamMetrics = createStreamMetrics();
streamMetrics.startTime = performance.now();
const rscFileStream = headlessRscHandler.rscStream;
rscFileStream.on("data", (chunk) => {
streamMetrics.chunks++;
streamMetrics.bytes += chunk.length;
});
rscFileStream.on("end", () => {
streamMetrics.duration = performance.now() - streamMetrics.startTime;
streamMetrics.endTime = performance.now();
rscHeadlessMetrics.streamMetrics = streamMetrics;
rscHeadlessMetrics.chunkRate = streamMetrics.chunks / (streamMetrics.duration / 1e3);
rscHeadlessMetrics.processingTime = streamMetrics.duration;
rscHeadlessMetrics.memoryUsage = process.memoryUsage();
rscHeadlessMetrics.chunks = streamMetrics.chunks;
});
rscFileStream.pipe(destination);
return destination;
},
abort: () => headlessRscHandler.abort()
};
const htmlStreamWrapper = {
pipe: (destination) => {
return htmlTransformStream.pipe(destination);
},
abort: () => {
htmlTransformStream.abort();
},
on: (event, listener) => {
if (event === "error") {
const htmlStream = htmlTransformStream.htmlStream;
if (htmlStream && typeof htmlStream.on === "function") {
htmlStream.on("error", listener);
}
}
return htmlStreamWrapper;
}
};
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`HTML stream timeout for route ${handlerOptions.route}`));
}, handlerOptions.htmlTimeout || 15e3);
if (htmlStreamErrored) {
clearTimeout(timeout);
resolve();
return;
}
let resolved = false;
const originalPipe = htmlTransformStream.pipe;
htmlTransformStream.pipe = function(destination) {
const result = originalPipe.call(this, destination);
destination.on("finish", () => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
resolve();
}
});
destination.on("error", (error) => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
reject(error);
}
});
return result;
};
setTimeout(() => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
resolve();
}
}, 100);
});
if (htmlStreamErrored) {
yield {
type: "error",
error: htmlStreamError || new Error("HTML stream failed"),
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
return;
}
yield {
type: "success",
html: htmlStreamWrapper,
rsc: rscStreamWrapper,
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
} catch (err) {
if (handlerOptions.verbose) {
handlerOptions.logger?.error(`[renderPage.server] Error: ${JSON.stringify(err)}`);
}
try {
if (headlessRscHandler) headlessRscHandler.abort();
if (fullRscHandler) fullRscHandler.abort();
if (htmlTransformStream) htmlTransformStream.abort();
} catch (cleanupError) {
handlerOptions.logger?.warn(`Failed to cleanup streams on error: ${cleanupError}`);
}
const panicError = handleError({
error: err,
critical: false,
logger: handlerOptions.logger,
panicThreshold: handlerOptions.panicThreshold,
context: `RenderPage Error (${handlerOptions.route})`
});
if (panicError != null) {
yield {
type: "error",
error: panicError,
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
} else {
yield {
type: "skip",
reason: err,
html: {
pipe: (destination) => {
destination.end();
return destination;
},
abort: () => {
}
},
rsc: {
pipe: (destination) => {
destination.end();
return destination;
},
abort: () => {
}
},
metrics: {
rscFull: rscFullMetrics,
rscHeadless: rscHeadlessMetrics,
html: htmlMetrics
}
};
}
}
};
export { renderPage };
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVuZGVyUGFnZS5zZXJ2ZXIuanMiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3BsdWdpbi9yZWFjdC1zdGF0aWMvcmVuZGVyUGFnZS5zZXJ2ZXIudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiByZW5kZXJQYWdlLnNlcnZlci50c1xuICpcbiAqIFBVUlBPU0U6IFNlcnZlci1zaWRlIHN0YXRpYyBwYWdlIHJlbmRlcmluZyBmb3IgUmVhY3QgU2VydmVyIENvbXBvbmVudHNcbiAqXG4gKiBBUkNISVRFQ1RVUkUgT1ZFUlZJRVc6XG4gKiBcbiAqIFNFUlZFUi1TSURFIHZzIENMSUVOVC1TSURFOlxuICogLSBTZXJ2ZXItc2lkZTogUlNDIGdlbmVyYXRpb24gaW4gbWFpbiB0aHJlYWQsIEhUTUwgZ2VuZXJhdGlvbiBpbiB3b3JrZXJcbiAqIC0gQ2xpZW50LXNpZGU6IFJTQyBnZW5lcmF0aW9uIGluIHdvcmtlciwgSFRNTCBnZW5lcmF0aW9uIGluIG1haW4gdGhyZWFkXG4gKiBcbiAqIEZMT1c6XG4gKiAxLiBDcmVhdGUgaGVhZGxlc3MgUlNDIHN0cmVhbSAoZm9yIC5yc2MgZmlsZSlcbiAqIDIuIENyZWF0ZSBmdWxsIFJTQyBzdHJlYW0gKGZvciBIVE1MIGdlbmVyYXRpb24pXG4gKiAzLiBDcmVhdGUgSFRNTCB0cmFuc2Zvcm0gc3RyZWFtIHRoYXQgY29udmVydHMgUlNDIHRvIEhUTUxcbiAqIDQuIEJvdGggc3RyZWFtcyBhcmUgcGlwZWQgdG8gZmlsZSB3cml0ZXJzXG4gKiBcbiAqIFNJTVBMSUZJRUQgQVBQUk9BQ0g6XG4gKiBUaGlzIGltcGxlbWVudGF0aW9uIGZvbGxvd3MgdGhlIHNhbWUgc2ltcGxlIHBhdHRlcm4gYXMgdGhlIGNsaWVudCBzaWRlLFxuICogYXZvaWRpbmcgY29tcGxleCBiYWNrcHJlc3N1cmUgaGFuZGxpbmcgYW5kIHJhY2UgY29uZGl0aW9ucy5cbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVSZW5kZXJNZXRyaWNzIH0gZnJvbSBcIi4uL21ldHJpY3MvY3JlYXRlUmVuZGVyTWV0cmljcy5qc1wiO1xuaW1wb3J0IHsgcm91dGVUb1VSTCB9IGZyb20gXCIuLi91dGlscy9yb3V0ZVRvVVJMLmpzXCI7XG5pbXBvcnQgdHlwZSB7IFJlbmRlclBhZ2VGbiB9IGZyb20gXCIuL3R5cGVzLmpzXCI7XG5pbXBvcnQgeyBoYW5kbGVFcnJvciB9IGZyb20gXCIuLi9lcnJvci9oYW5kbGVFcnJvci5qc1wiO1xuaW1wb3J0IHsgYXNzZXJ0UmVhY3RTZXJ2ZXIgfSBmcm9tIFwiLi4vY29uZmlnL2dldENvbmRpdGlvbi5qc1wiO1xuXG5pbXBvcnQgeyByZW5kZXJSc2NTdHJlYW0gfSBmcm9tIFwiLi4vc3RyZWFtL3JlbmRlclJzY1N0cmVhbS5zZXJ2ZXIuanNcIjtcbmltcG9ydCB7IGNyZWF0ZU1haW5UaHJlYWRIYW5kbGVycyB9IGZyb20gXCIuLi9zdHJlYW0vY3JlYXRlTWFpblRocmVhZEhhbmRsZXJzLmpzXCI7XG5pbXBvcnQgeyBjcmVhdGVSc2NUb0h0bWxTdHJlYW0gfSBmcm9tIFwiLi9yc2NUb0h0bWxTdHJlYW0uc2VydmVyLmpzXCI7XG5pbXBvcnQgeyByZXNvbHZlQ29tcG9uZW50IH0gZnJvbSBcIi4uL2hlbHBlcnMvcmVzb2x2ZUNvbXBvbmVudC5qc1wiO1xuaW1wb3J0IHsgcmVzb2x2ZVBhZ2VBbmRQcm9wcyB9IGZyb20gXCIuLi9oZWxwZXJzL3Jlc29sdmVQYWdlQW5kUHJvcHMuanNcIjtcbmltcG9ydCB7IFJvb3QgYXMgRGVmYXVsdFJvb3QgfSBmcm9tIFwiLi4vY29tcG9uZW50cy9yb290LmpzXCI7XG5pbXBvcnQgeyBIdG1sIGFzIERlZmF1bHRIdG1sIH0gZnJvbSBcIi4uL2NvbXBvbmVudHMvaHRtbC5qc1wiO1xuaW1wb3J0IHsgY3JlYXRlU3RyZWFtTWV0cmljcyB9IGZyb20gXCIuLi9tZXRyaWNzL2NyZWF0ZVN0cmVhbU1ldHJpY3MuanNcIjtcbmltcG9ydCB7IGpvaW4gfSBmcm9tIFwibm9kZTpwYXRoXCI7XG5pbXBvcnQgeyBjcmVhdGVIZWFkbGVzc1N0cmVhbVN0YXRlLCB0cmFja0hlYWRsZXNzU3RyZWFtRXJyb3IsIGhhc0hlYWRsZXNzU3RyZWFtRXJyb3IgfSBmcm9tIFwiLi4vaGVscGVycy9oZWFkbGVzc1N0cmVhbVN0YXRlLmpzXCI7XG5cbmV4cG9ydCBjb25zdCByZW5kZXJQYWdlOiBSZW5kZXJQYWdlRm4gPSBhc3luYyBmdW5jdGlvbiogcmVuZGVyUGFnZShcbiAgaGFuZGxlck9wdGlvbnNcbikge1xuICAvLyBFbnN1cmUgd2UncmUgaW4gdGhlIGNvcnJlY3QgZW52aXJvbm1lbnRcbiAgYXNzZXJ0UmVhY3RTZXJ2ZXIoKTtcblxuICAvLyBDcmVhdGUgbWV0cmljcyB1cGZyb250IHdpdGggcHJvcGVyIHR5cGVzXG4gIGNvbnN0IGJhc2VEaXIgPSBqb2luKFxuICAgIGhhbmRsZXJPcHRpb25zLmJ1aWxkLm91dERpcixcbiAgICBoYW5kbGVyT3B0aW9ucy5idWlsZC5zdGF0aWNcbiAgKTtcbiAgY29uc3Qgcm91dGVQYXRoID0gaGFuZGxlck9wdGlvbnMucm91dGUucmVwbGFjZSgvXlxcLy8sIFwiXCIpO1xuXG4gIGNvbnN0IGh0bWxNZXRyaWNzID0gY3JlYXRlUmVuZGVyTWV0cmljcyh7XG4gICAgcm91dGU6IGhhbmRsZXJPcHRpb25zLnJvdXRlLFxuICAgIHR5cGU6IFwiaHRtbFwiLFxuICAgIGZyb21NYWluVGhyZWFkOiBmYWxzZSwgLy8gU2VydmVyOiBIVE1MIHJlbmRlcmVkIGluIHdvcmtlclxuICAgIGZyb21Sc2NXb3JrZXI6IGZhbHNlLFxuICAgIGZyb21IdG1sV29ya2VyOiB0cnVlLFxuICAgIGJhc2VEaXIsXG4gICAgcm91dGVQYXRoLFxuICAgIGZpbGVOYW1lOiBoYW5kbGVyT3B0aW9ucy5idWlsZC5odG1sT3V0cHV0UGF0aCxcbiAgICBvdXRwdXRQYXRoOiBqb2luKGJhc2VEaXIsIHJvdXRlUGF0aCwgaGFuZGxlck9wdGlvbnMuYnVpbGQuaHRtbE91dHB1dFBhdGgpLFxuICB9KTtcbiAgXG4gIGNvbnN0IHJzY0Z1bGxNZXRyaWNzID0gY3JlYXRlUmVuZGVyTWV0cmljcyh7XG4gICAgcm91dGU6IGhhbmRsZXJPcHRpb25zLnJvdXRlLFxuICAgIHR5cGU6IFwicnNjLWZ1bGxcIixcbiAgICBmcm9tTWFpblRocmVhZDogdHJ1ZSwgLy8gU2VydmVyOiBSU0MgcmVuZGVyZWQgb24gbWFpbiB0aHJlYWRcbiAgICBmcm9tUnNjV29ya2VyOiBmYWxzZSxcbiAgICBmcm9tSHRtbFdvcmtlcjogZmFsc2UsXG4gIH0pO1xuICBcbiAgY29uc3QgcnNjSGVhZGxlc3NNZXRyaWNzID0gY3JlYXRlUmVuZGVyTWV0cmljcyh7XG4gICAgcm91dGU6IGhhbmRsZXJPcHRpb25zLnJvdXRlLFxuICAgIHR5cGU6IFwicnNjLWhlYWRsZXNzXCIsXG4gICAgZnJvbU1haW5UaHJlYWQ6IHRydWUsIC8vIFNlcnZlcjogUlNDIHJlbmRlcmVkIG9uIG1haW4gdGhyZWFkXG4gICAgZnJvbVJzY1dvcmtlcjogZmFsc2UsXG4gICAgZnJvbUh0bWxXb3JrZXI6IGZhbHNlLFxuICAgIGJhc2VEaXIsXG4gICAgcm91dGVQYXRoLFxuICAgIGZpbGVOYW1lOiBoYW5kbGVyT3B0aW9ucy5idWlsZC5yc2NPdXRwdXRQYXRoLFxuICAgIG91dHB1dFBhdGg6IGpvaW4oYmFzZURpciwgcm91dGVQYXRoLCBoYW5kbGVyT3B0aW9ucy5idWlsZC5yc2NPdXRwdXRQYXRoKSxcbiAgfSk7XG5cbiAgLy8gRGVjbGFyZSB2YXJpYWJsZXMgb3V0c2lkZSB0cnkgYmxvY2tcbiAgbGV0IGhlYWRsZXNzUnNjSGFuZGxlcjogYW55ID0gbnVsbDtcbiAgbGV0IGZ1bGxSc2NIYW5kbGVyOiBhbnkgPSBudWxsO1xuICBsZXQgaHRtbFRyYW5zZm9ybVN0cmVhbTogYW55ID0gbnVsbDtcbiAgXG4gIC8vIEVycm9yIHRyYWNraW5nIHZhcmlhYmxlcyBmb3IgaGVhZGxlc3Mgc3RyZWFtXG4gIGxldCBoZWFkbGVzc1N0cmVhbUVycm9yZWQgPSBmYWxzZTtcbiAgbGV0IGhlYWRsZXNzRXJyb3I6IEVycm9yIHwgbnVsbCA9IG51bGw7XG4gIFxuICAvLyBFcnJvciB0cmFja2luZyB2YXJpYWJsZXMgZm9yIEhUTUwgc3RyZWFtXG4gIGxldCBodG1sU3RyZWFtRXJyb3JlZCA9IGZhbHNlO1xuICBsZXQgaHRtbFN0cmVhbUVycm9yOiBFcnJvciB8IG51bGwgPSBudWxsO1xuXG4gIC8vIFNlcnZlci1zaWRlIHN0cmVhbSByZXVzZSBzdG9yYWdlIChzaW1pbGFyIHRvIGNsaWVudC1zaWRlIGhlYWRsZXNzU3RyZWFtRWxlbWVudHMpXG4gIGNvbnN0IGhlYWRsZXNzU3RyZWFtU3RhdGUgPSBjcmVhdGVIZWFkbGVzc1N0cmVhbVN0YXRlKCk7XG5cbiAgdHJ5IHtcbiAgICBpZiAoaGFuZGxlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy5pbmZvKFxuICAgICAgICBgW3JlbmRlclBhZ2Uuc2VydmVyXSBTZXJ2ZXItc2lkZSByZW5kZXJpbmcgZm9yIHJvdXRlOiAke2hhbmRsZXJPcHRpb25zLnJvdXRlfWBcbiAgICAgICk7XG4gICAgfVxuXG4gICAgLy8gU2V0IFVSTCBpZiBub3QgcHJvdmlkZWRcbiAgICBpZiAoIWhhbmRsZXJPcHRpb25zLnVybCkge1xuICAgICAgaGFuZGxlck9wdGlvbnMudXJsID0gcm91dGVUb1VSTChcbiAgICAgICAgaGFuZGxlck9wdGlvbnMucm91dGUsXG4gICAgICAgIGhhbmRsZXJPcHRpb25zLm1vZHVsZUJhc2VVUkwsXG4gICAgICAgIGhhbmRsZXJPcHRpb25zLmJ1aWxkLnJzY091dHB1dFBhdGhcbiAgICAgICk7XG4gICAgfVxuXG4gICAgLy8gUmVzb2x2ZSBjb21wb25lbnRzIGFuZCBwcm9wcyB1c2luZyB0aGUgcHJvcGVyIGhlbHBlclxuICAgIGxldCBQYWdlQ29tcG9uZW50OiBhbnkgPSBudWxsO1xuICAgIGxldCBSb290Q29tcG9uZW50OiBhbnkgPSBudWxsO1xuICAgIGxldCBIdG1sQ29tcG9uZW50OiBhbnkgPSBudWxsO1xuICAgIGxldCBwYWdlUHJvcHM6IGFueSA9IHt9OyAvLyBJbml0aWFsaXplIGFzIGVtcHR5IG9iamVjdCAtIHByb3BzIGZ1bmN0aW9uIHdpbGwgcG9wdWxhdGUgaXRcblxuICAgIC8vIFVzZSByZXNvbHZlUGFnZUFuZFByb3BzIGhlbHBlciB0byBwcm9wZXJseSBsb2FkIHBhZ2UgYW5kIHByb3BzXG4gICAgaWYgKGhhbmRsZXJPcHRpb25zLnBhZ2VQYXRoKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBwYWdlQW5kUHJvcHNSZXN1bHQgPSBhd2FpdCByZXNvbHZlUGFnZUFuZFByb3BzKHtcbiAgICAgICAgICBwYWdlUGF0aDogaGFuZGxlck9wdGlvbnMucGFnZVBhdGgsXG4gICAgICAgICAgcGFnZUV4cG9ydE5hbWU6IGhhbmRsZXJPcHRpb25zLnBhZ2VFeHBvcnROYW1lLFxuICAgICAgICAgIHByb3BzUGF0aDogaGFuZGxlck9wdGlvbnMucHJvcHNQYXRoLFxuICAgICAgICAgIHByb3BzRXhwb3J0TmFtZTogaGFuZGxlck9wdGlvbnMucHJvcHNFeHBvcnROYW1lLFxuICAgICAgICAgIGxvYWRlcjogaGFuZGxlck9wdGlvbnMubG9hZGVyLFxuICAgICAgICAgIHZlcmJvc2U6IGhhbmRsZXJPcHRpb25zLnZlcmJvc2UsXG4gICAgICAgICAgbG9nZ2VyOiBoYW5kbGVyT3B0aW9ucy5sb2dnZXIsXG4gICAgICAgICAgcm91dGU6IGhhbmRsZXJPcHRpb25zLnJvdXRlLFxuICAgICAgICAgIHVybDogaGFuZGxlck9wdGlvbnMudXJsLFxuICAgICAgICAgIG1vZHVsZUJhc2VVUkw6IGhhbmRsZXJPcHRpb25zLm1vZHVsZUJhc2VVUkwsXG4gICAgICAgICAgYnVpbGQ6IHtcbiAgICAgICAgICAgIHJzY091dHB1dFBhdGg6IGhhbmRsZXJPcHRpb25zLmJ1aWxkLnJzY091dHB1dFBhdGgsXG4gICAgICAgICAgfSxcbiAgICAgICAgfSk7XG5cbiAgICAgICAgaWYgKHBhZ2VBbmRQcm9wc1Jlc3VsdC50eXBlID09PSBcInN1Y2Nlc3NcIikge1xuICAgICAgICAgIFBhZ2VDb21wb25lbnQgPSBwYWdlQW5kUHJvcHNSZXN1bHQuUGFnZUNvbXBvbmVudDtcbiAgICAgICAgICAvLyBBbHdheXMgdXNlIHRoZSBwcm9wcyByZXR1cm5lZCBmcm9tIHRoZSBwcm9wcyBmdW5jdGlvblxuICAgICAgICAgIC8vIFJvb3QgY29tcG9uZW50cyBjYW4gaGFuZGxlIGVtcHR5IHByb3BzIHdpdGggdGhlaXIgZGVmYXVsdHNcbiAgICAgICAgICBwYWdlUHJvcHMgPSBwYWdlQW5kUHJvcHNSZXN1bHQucGFnZVByb3BzIHx8IHt9O1xuICAgICAgICAgIFxuICAgICAgICAgIGlmIChoYW5kbGVyT3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICAgICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgICAgICAgIGBbcmVuZGVyUGFnZS5zZXJ2ZXJdIFN1Y2Nlc3NmdWxseSBsb2FkZWQgcGFnZSBhbmQgcHJvcHMgZm9yIHJvdXRlICR7aGFuZGxlck9wdGlvbnMucm91dGV9OiBwYWdlUHJvcHM9JHtKU09OLnN0cmluZ2lmeShwYWdlUHJvcHMpfWBcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8ud2FybihcbiAgICAgICAgICAgIGBGYWlsZWQgdG8gbG9hZCBwYWdlIGFuZCBwcm9wcyBmcm9tICR7aGFuZGxlck9wdGlvbnMucGFnZVBhdGh9OiAke1xuICAgICAgICAgICAgICBwYWdlQW5kUHJvcHNSZXN1bHQuZXJyb3I/Lm1lc3NhZ2UgfHwgXCJVbmtub3duIGVycm9yXCJcbiAgICAgICAgICAgIH1gXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy53YXJuKFxuICAgICAgICAgIGBFcnJvciBsb2FkaW5nIHBhZ2UgYW5kIHByb3BzIGZyb20gJHtoYW5kbGVyT3B0aW9ucy5wYWdlUGF0aH06ICR7XG4gICAgICAgICAgICBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcilcbiAgICAgICAgICB9YFxuICAgICAgICApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIExvYWQgUm9vdCBjb21wb25lbnRcbiAgICBpZiAoaGFuZGxlck9wdGlvbnMucm9vdFBhdGgpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHJvb3RSZXN1bHQgPSBhd2FpdCByZXNvbHZlQ29tcG9uZW50KHtcbiAgICAgICAgICBjb21wb25lbnRQYXRoOiBoYW5kbGVyT3B0aW9ucy5yb290UGF0aCxcbiAgICAgICAgICBleHBvcnROYW1lOiBoYW5kbGVyT3B0aW9ucy5yb290RXhwb3J0TmFtZSxcbiAgICAgICAgICBsb2FkZXI6IGhhbmRsZXJPcHRpb25zLmxvYWRlcixcbiAgICAgICAgfSk7XG4gICAgICAgIGlmIChyb290UmVzdWx0LnR5cGUgPT09IFwic3VjY2Vzc1wiKSB7XG4gICAgICAgICAgUm9vdENvbXBvbmVudCA9IHJvb3RSZXN1bHQuY29tcG9uZW50O1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8ud2FybihcbiAgICAgICAgICAgIGBGYWlsZWQgdG8gbG9hZCBSb290IGNvbXBvbmVudCBmcm9tICR7aGFuZGxlck9wdGlvbnMucm9vdFBhdGh9OiAke1xuICAgICAgICAgICAgICByb290UmVzdWx0LmVycm9yPy5tZXNzYWdlIHx8IFwiVW5rbm93biBlcnJvclwiXG4gICAgICAgICAgICB9YFxuICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8ud2FybihcbiAgICAgICAgICBgRXJyb3IgbG9hZGluZyBSb290IGNvbXBvbmVudCBmcm9tICR7aGFuZGxlck9wdGlvbnMucm9vdFBhdGh9OiAke1xuICAgICAgICAgICAgZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpXG4gICAgICAgICAgfWBcbiAgICAgICAgKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBMb2FkIEh0bWwgY29tcG9uZW50XG4gICAgaWYgKGhhbmRsZXJPcHRpb25zLmh0bWxQYXRoKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBodG1sUmVzdWx0ID0gYXdhaXQgcmVzb2x2ZUNvbXBvbmVudCh7XG4gICAgICAgICAgY29tcG9uZW50UGF0aDogaGFuZGxlck9wdGlvbnMuaHRtbFBhdGgsXG4gICAgICAgICAgZXhwb3J0TmFtZTogaGFuZGxlck9wdGlvbnMuaHRtbEV4cG9ydE5hbWUsXG4gICAgICAgICAgbG9hZGVyOiBoYW5kbGVyT3B0aW9ucy5sb2FkZXIsXG4gICAgICAgIH0pO1xuICAgICAgICBpZiAoaHRtbFJlc3VsdC50eXBlID09PSBcInN1Y2Nlc3NcIikge1xuICAgICAgICAgIEh0bWxDb21wb25lbnQgPSBodG1sUmVzdWx0LmNvbXBvbmVudDtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/Lndhcm4oXG4gICAgICAgICAgICBgRmFpbGVkIHRvIGxvYWQgSHRtbCBjb21wb25lbnQgZnJvbSAke2hhbmRsZXJPcHRpb25zLmh0bWxQYXRofTogJHtcbiAgICAgICAgICAgICAgaHRtbFJlc3VsdC5lcnJvcj8ubWVzc2FnZSB8fCBcIlVua25vd24gZXJyb3JcIlxuICAgICAgICAgICAgfWBcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/Lndhcm4oXG4gICAgICAgICAgYEVycm9yIGxvYWRpbmcgSHRtbCBjb21wb25lbnQgZnJvbSAke2hhbmRsZXJPcHRpb25zLmh0bWxQYXRofTogJHtcbiAgICAgICAgICAgIGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKVxuICAgICAgICAgIH1gXG4gICAgICAgICk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gVXNlIGRlZmF1bHRzIGlmIGNvbXBvbmVudHMgYXJlIHN0aWxsIG5vdCBsb2FkZWRcbiAgICBpZiAoIVJvb3RDb21wb25lbnQpIHtcbiAgICAgIFJvb3RDb21wb25lbnQgPSBEZWZhdWx0Um9vdCBhcyBhbnk7XG4gICAgfVxuICAgIGlmICghSHRtbENvbXBvbmVudCkge1xuICAgICAgSHRtbENvbXBvbmVudCA9IERlZmF1bHRIdG1sIGFzIGFueTtcbiAgICB9XG5cbiAgICAvLyBFbnN1cmUgd2UgaGF2ZSBhbGwgcmVxdWlyZWQgY29tcG9uZW50c1xuICAgIGlmICghUGFnZUNvbXBvbmVudCB8fCAhUm9vdENvbXBvbmVudCB8fCAhSHRtbENvbXBvbmVudCkge1xuICAgICAgeWllbGQge1xuICAgICAgICB0eXBlOiBcImVycm9yXCIsXG4gICAgICAgIGVycm9yOiBuZXcgRXJyb3IoXG4gICAgICAgICAgYENvbXBvbmVudCByZXNvbHV0aW9uIGZhaWxlZDogbWlzc2luZyByZXF1aXJlZCBjb21wb25lbnRzIChQYWdlOiAkeyEhUGFnZUNvbXBvbmVudH0sIFJvb3Q6ICR7ISFSb290Q29tcG9uZW50fSwgSHRtbDogJHshIUh0bWxDb21wb25lbnR9KWBcbiAgICAgICAgKSxcbiAgICAgICAgbWV0cmljczoge1xuICAgICAgICAgIHJzY0Z1bGw6IHJzY0Z1bGxNZXRyaWNzLFxuICAgICAgICAgIHJzY0hlYWRsZXNzOiByc2NIZWFkbGVzc01ldHJpY3MsXG4gICAgICAgICAgaHRtbDogaHRtbE1ldHJpY3MsXG4gICAgICAgIH0sXG4gICAgICB9O1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIC8vIENyZWF0ZSBoYW5kbGVyIG9wdGlvbnMgd2l0aCByZXNvbHZlZCBjb21wb25lbnRzIGFuZCBwcm9wc1xuICAgIGNvbnN0IHVuaXF1ZUlkID0gaGFuZGxlck9wdGlvbnMuaWQgPz8gYCR7aGFuZGxlck9wdGlvbnMucm91dGV9P2lkPSR7RGF0ZS5ub3coKX0tJHtNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zdWJzdHJpbmcoMiwgMTEpfWA7XG4gICAgY29uc3QgbmV3SGFuZGxlck9wdGlvbnMgPSB7XG4gICAgICAuLi5oYW5kbGVyT3B0aW9ucyxcbiAgICAgIGlkOiB1bmlxdWVJZCxcbiAgICAgIHVybDogYCR7aGFuZGxlck9wdGlvbnMudXJsfWAsXG4gICAgICByb3V0ZTogYCR7aGFuZGxlck9wdGlvbnMucm91dGV9YCxcbiAgICAgIFBhZ2VDb21wb25lbnQsXG4gICAgICBSb290Q29tcG9uZW50LFxuICAgICAgSHRtbENvbXBvbmVudCxcbiAgICAgIHBhZ2VQcm9wcyxcbiAgICB9O1xuICAgIFxuICAgIGlmIChoYW5kbGVyT3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgIGBbcmVuZGVyUGFnZS5zZXJ2ZXJdIENyZWF0ZWQgbmV3SGFuZGxlck9wdGlvbnMgZm9yIHJvdXRlICR7aGFuZGxlck9wdGlvbnMucm91dGV9IHdpdGggcGFnZVByb3BzOiAke0pTT04uc3RyaW5naWZ5KHBhZ2VQcm9wcyl9YFxuICAgICAgKTtcbiAgICB9XG5cbiAgICAvLyBDcmVhdGUgaGVhZGxlc3MgUlNDIGhhbmRsZXIgKGZvciAucnNjIGZpbGUpIC0gd2l0aCBwcm9wZXIgZXJyb3IgaGFuZGxpbmdcbiAgICBjb25zdCBoZWFkbGVzc0hhbmRsZXJzID0gY3JlYXRlTWFpblRocmVhZEhhbmRsZXJzKFxuICAgICAgaGFuZGxlck9wdGlvbnMsXG4gICAgICAoZXJyb3IsIGlzUGFuaWMpID0+IHtcbiAgICAgICAgaWYgKGhhbmRsZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgICAgICBgW3JlbmRlclBhZ2Uuc2VydmVyXSBIZWFkbGVzcyBzdHJlYW0gZXJyb3IgaGFuZGxlciBjYWxsZWQgZm9yIHJvdXRlICR7aGFuZGxlck9wdGlvbnMucm91dGV9OiAke2Vycm9yLm1lc3NhZ2V9LCBpc1BhbmljOiAke2lzUGFuaWN9YFxuICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgXG4gICAgICAgIC8vIFRyYWNrIGlmIHRoZSBoZWFkbGVzcyBzdHJlYW0gaGFkIGVycm9yc1xuICAgICAgICBoZWFkbGVzc1N0cmVhbUVycm9yZWQgPSB0cnVlO1xuICAgICAgICBoZWFkbGVzc0Vycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFwiSGVhZGxlc3MgUlNDIHN0cmVhbSBmYWlsZWRcIik7XG4gICAgICAgIFxuICAgICAgICAvLyBUcmFjayBoZWFkbGVzcyBzdHJlYW0gZXJyb3JzIGZvciBjb25kaXRpb25hbCByZXVzZSBsb2dpYyAobGlrZSBSU0Mgd29ya2VyKVxuICAgICAgICB0cmFja0hlYWRsZXNzU3RyZWFtRXJyb3IoaGVhZGxlc3NTdHJlYW1TdGF0ZSwgaGFuZGxlck9wdGlvbnMucm91dGUsIGhlYWRsZXNzRXJyb3IpO1xuICAgICAgICBcbiAgICAgICAgaWYgKGhhbmRsZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgICAgICBgW3JlbmRlclBhZ2Uuc2VydmVyXSBTdG9yZWQgaGVhZGxlc3Mgc3RyZWFtIGVycm9yIGZvciByb3V0ZSAke2hhbmRsZXJPcHRpb25zLnJvdXRlfSBpbiBoZWFkbGVzc1N0cmVhbUVycm9ycyBtYXBgXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICBcbiAgICAgICAgLy8gU3RvcmUgcGFuaWMgZXJyb3JzIGZvciBsYXRlciBoYW5kbGluZ1xuICAgICAgICBpZiAoaXNQYW5pYykge1xuICAgICAgICAgIC8vIEZvciBwYW5pYyB0aHJlc2hvbGQgXCJhbGxfZXJyb3JzXCIsIHRoZSBwYW5pYyBlcnJvciB3aWxsIGJlIGhhbmRsZWQgYnkgcmVuZGVyUGFnZXNcbiAgICAgICAgICBpZiAoaGFuZGxlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy5pbmZvKFxuICAgICAgICAgICAgICBgW3JlbmRlclBhZ2Uuc2VydmVyXSBQYW5pYyBlcnJvciBkZXRlY3RlZCBmb3Igcm91dGUgJHtoYW5kbGVyT3B0aW9ucy5yb3V0ZX0sIHdpbGwgYmUgaGFuZGxlZCBieSByZW5kZXJQYWdlc2BcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgKTtcbiAgICBcbiAgICAvLyBPdmVycmlkZSBvbkRhdGEgdG8gdHJhY2sgbWV0cmljc1xuICAgIGhlYWRsZXNzSGFuZGxlcnMub25EYXRhID0gKF9pZCwgY2h1bmspID0+IHtcbiAgICAgIHJzY0hlYWRsZXNzTWV0cmljcy5jaHVua3MrKztcbiAgICAgIHJzY0hlYWRsZXNzTWV0cmljcy5zdHJlYW1NZXRyaWNzLmJ5dGVzICs9IGNodW5rLmxlbmd0aDtcbiAgICB9O1xuICAgIFxuICAgIGhlYWRsZXNzUnNjSGFuZGxlciA9IHJlbmRlclJzY1N0cmVhbShcbiAgICAgIHtcbiAgICAgICAgLi4ubmV3SGFuZGxlck9wdGlvbnMsXG4gICAgICAgIGh0bWxQYXRoOiAnJywgLy8gSGVhZGxlc3MgUlNDIC0gbm8gSFRNTCB3cmFwcGVyXG4gICAgICAgIC8vIElmIHdlIGV4cGVjdCBlcnJvcnMsIHByb3ZpZGUgYSBzYWZlIFBhZ2UgY29tcG9uZW50IHRoYXQgZG9lc24ndCB0aHJvd1xuICAgICAgICBQYWdlQ29tcG9uZW50OiBuZXdIYW5kbGVyT3B0aW9ucy5QYWdlQ29tcG9uZW50LCAvLyBVc2Ugb3JpZ2luYWwgZm9yIG5vdywgd2lsbCBiZSBvdmVycmlkZGVuIGlmIGVycm9ycyBvY2N1clxuICAgICAgfSxcbiAgICAgIGhlYWRsZXNzSGFuZGxlcnNcbiAgICApO1xuICAgIFxuICAgIC8vIE5vdGU6IFBhbmljIGVycm9ycyB3aWxsIGJlIHlpZWxkZWQgZnJvbSB0aGUgZXJyb3IgaGFuZGxlciB3aGVuIHRoZXkgb2NjdXJcbiAgICAvLyBObyBuZWVkIHRvIGNoZWNrIHNob3VsZFlpZWxkUGFuaWNFcnJvciBoZXJlIGFzIGl0J3Mgc2V0IGFzeW5jaHJvbm91c2x5XG5cbiAgICAvLyBTdG9yZSBQYWdlQ29tcG9uZW50IGZvciByZXVzZSB3aGVuIGhlYWRsZXNzIHN0cmVhbSBjb21wbGV0ZXMgKGxpa2UgUlNDIHdvcmtlcilcbiAgICBoZWFkbGVzc1JzY0hhbmRsZXIucnNjU3RyZWFtLm9uKCdlbmQnLCAoKSA9PiB7XG4gICAgICAvLyBPbmx5IHN0b3JlIGlmIHRoaXMgaXMgYSBoZWFkbGVzcyBzdHJlYW0gYW5kIG5vIGVycm9ycyBvY2N1cnJlZCAobGlrZSBSU0Mgd29ya2VyKVxuICAgICAgaWYgKCFoYXNIZWFkbGVzc1N0cmVhbUVycm9yKGhlYWRsZXNzU3RyZWFtU3RhdGUsIGhhbmRsZXJPcHRpb25zLnJvdXRlKSkge1xuICAgICAgICBoZWFkbGVzc1N0cmVhbVN0YXRlLmVsZW1lbnRzLnNldCh1bmlxdWVJZCwge1xuICAgICAgICAgIFBhZ2VDb21wb25lbnQ6IG5ld0hhbmRsZXJPcHRpb25zLlBhZ2VDb21wb25lbnQsXG4gICAgICAgICAgZXJyb3JlZDogZmFsc2VcbiAgICAgICAgfSk7XG4gICAgICAgIGlmIChoYW5kbGVyT3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy5pbmZvKGBbcmVuZGVyUGFnZS5zZXJ2ZXJdIFN0b3JlZCBQYWdlQ29tcG9uZW50IGZvciBoZWFkbGVzcyBzdHJlYW0gJHt1bmlxdWVJZH1gKTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaWYgKGhhbmRsZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oYFtyZW5kZXJQYWdlLnNlcnZlcl0gSGVhZGxlc3Mgc3RyZWFtIGVycm9yZWQgZm9yIHJvdXRlICR7aGFuZGxlck9wdGlvbnMucm91dGV9LCBub3Qgc3RvcmluZyBQYWdlQ29tcG9uZW50IGZvciByZXVzZWApO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICAvLyBDcmVhdGUgZnVsbCBSU0MgaGFuZGxlciAoZm9yIEhUTUwgZ2VuZXJhdGlvbikgLSByZXVzZSBoZWFkbGVzcyBzdHJlYW0gZWxlbWVudHMgaWYgbm8gZXJyb3JzXG4gICAgLy8gRm9yIHNlcnZlci1zaWRlLCB3ZSBjcmVhdGUgYm90aCBzdHJlYW1zIGluIHBhcmFsbGVsIGxpa2UgdGhlIGNsaWVudC1zaWRlXG4gICAgbGV0IGZ1bGxQYW5pY0Vycm9yOiBFcnJvciB8IG51bGwgPSBudWxsO1xuICAgIGNvbnN0IGZ1bGxIYW5kbGVycyA9IGNyZWF0ZU1haW5UaHJlYWRIYW5kbGVycyhcbiAgICAgIGhhbmRsZXJPcHRpb25zLFxuICAgICAgKGVycm9yLCBpc1BhbmljKSA9PiB7XG4gICAgICAgIC8vIElmIHRoaXMgaXMgYSBwYW5pYyBlcnJvciwgc3RvcmUgaXQgdG8gYmUgaGFuZGxlZCBsYXRlclxuICAgICAgICBpZiAoaXNQYW5pYykge1xuICAgICAgICAgIGZ1bGxQYW5pY0Vycm9yID0gZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yIDogbmV3IEVycm9yKFwiRnVsbCBSU0Mgc3RyZWFtIGZhaWxlZFwiKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICk7XG4gICAgXG4gICAgLy8gT3ZlcnJpZGUgb25EYXRhIHRvIHRyYWNrIG1ldHJpY3NcbiAgICBmdWxsSGFuZGxlcnMub25EYXRhID0gKF9pZCwgY2h1bmspID0+IHtcbiAgICAgIHJzY0Z1bGxNZXRyaWNzLmNodW5rcysrO1xuICAgICAgcnNjRnVsbE1ldHJpY3Muc3RyZWFtTWV0cmljcy5ieXRlcyArPSBjaHVuay5sZW5ndGg7XG4gICAgfTtcbiAgICBcbiAgICAvLyBDcmVhdGUgZnVsbCBSU0MgaGFuZGxlciBvcHRpb25zIC0gdXNlIFJlYWN0LkZyYWdtZW50IGlmIGhlYWRsZXNzIHN0cmVhbSBoYWQgZXJyb3JzIChsaWtlIFJTQyB3b3JrZXIpXG4gICAgLy8gQ2hlY2sgaWYgdGhlcmUgYXJlIGFueSBleGlzdGluZyBoZWFkbGVzcyBzdHJlYW0gZXJyb3JzIGZvciB0aGlzIHJvdXRlXG4gICAgY29uc3QgaGFzRXhpc3RpbmdIZWFkbGVzc0Vycm9yID0gaGFzSGVhZGxlc3NTdHJlYW1FcnJvcihoZWFkbGVzc1N0cmVhbVN0YXRlLCBoYW5kbGVyT3B0aW9ucy5yb3V0ZSk7XG4gICAgY29uc3Qgc2hvdWxkVXNlRmFsbGJhY2sgPSBoZWFkbGVzc1N0cmVhbUVycm9yZWQgfHwgaGFzRXhpc3RpbmdIZWFkbGVzc0Vycm9yO1xuICAgIFxuICAgIGlmIChoYW5kbGVyT3B0aW9ucy52ZXJib3NlKSB7XG4gICAgICBoYW5kbGVyT3B0aW9ucy5sb2dnZXI/LmluZm8oXG4gICAgICAgIGBbcmVuZGVyUGFnZS5zZXJ2ZXJdIENyZWF0aW5nIGZ1bGwgUlNDIGhhbmRsZXIgb3B0aW9ucyBmb3Igcm91dGUgJHtoYW5kbGVyT3B0aW9ucy5yb3V0ZX06IGhlYWRsZXNzU3RyZWFtRXJyb3JlZD0ke2hlYWRsZXNzU3RyZWFtRXJyb3JlZH0sIGhhc0V4aXN0aW5nSGVhZGxlc3NFcnJvcj0ke2hhc0V4aXN0aW5nSGVhZGxlc3NFcnJvcn0sIHNob3VsZFVzZUZhbGxiYWNrPSR7c2hvdWxkVXNlRmFsbGJhY2t9YFxuICAgICAgKTtcbiAgICB9XG4gICAgXG4gICAgLy8gQ3JlYXRlIGEgd3JhcHBlciBQYWdlQ29tcG9uZW50IHRoYXQgcmV0dXJucyBudWxsIGlmIHRoZXJlIGFyZSBoZWFkbGVzcyBzdHJlYW0gZXJyb3JzXG4gICAgY29uc3QgU2FmZVBhZ2VDb21wb25lbnQgPSAocHJvcHM6IGFueSkgPT4ge1xuICAgICAgLy8gQ2hlY2sgaWYgdGhlcmUgYXJlIGFueSBoZWFkbGVzcyBzdHJlYW0gZXJyb3JzIGZvciB0aGlzIHJvdXRlXG4gICAgICBjb25zdCBoYXNFcnJvciA9IGhhc0hlYWRsZXNzU3RyZWFtRXJyb3IoaGVhZGxlc3NTdHJlYW1TdGF0ZSwgaGFuZGxlck9wdGlvbnMucm91dGUpO1xuICAgICAgaWYgKGhhc0Vycm9yKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuICAgICAgcmV0dXJuIG5ld0hhbmRsZXJPcHRpb25zLlBhZ2VDb21wb25lbnQocHJvcHMpO1xuICAgIH07XG4gICAgXG4gICAgY29uc3QgZnVsbFJzY0hhbmRsZXJPcHRpb25zID0ge1xuICAgICAgLi4ubmV3SGFuZGxlck9wdGlvbnMsXG4gICAgICBodG1sUGF0aDogdW5kZWZpbmVkLCAvLyBGdWxsIFJTQyAtIGluY2x1ZGUgSFRNTCB3cmFwcGVyXG4gICAgICBoZWFkbGVzc1N0cmVhbUVsZW1lbnRzOiBoZWFkbGVzc1N0cmVhbVN0YXRlLmVsZW1lbnRzLCAvLyBQYXNzIHRoZSBzdG9yYWdlIG1hcCBmb3IgcmV1c2VcbiAgICAgIC8vIFVzZSBTYWZlUGFnZUNvbXBvbmVudCB0aGF0IHJldHVybnMgbnVsbCB3aGVuIHRoZXJlIGFyZSBoZWFkbGVzcyBzdHJlYW0gZXJyb3JzXG4gICAgICBQYWdlQ29tcG9uZW50OiBTYWZlUGFnZUNvbXBvbmVudCxcbiAgICB9O1xuICAgIFxuICAgIFxuICAgIC8vIENyZWF0ZSBhIFBhZ2VDb21wb25lbnQgdGhhdCB1c2VzIFJlYWN0LnVzZSgpIHRvIGNvbnN1bWUgdGhlIGhlYWRsZXNzIHN0cmVhbSBhbmQgY2hlY2sgZm9yIGVycm9yc1xuXG4gICAgLy8gU3RvcmUgdGhlIGhlYWRsZXNzIHN0cmVhbSBlbGVtZW50cyBmb3IgcmV1c2UgKGxpa2UgUlNDIHdvcmtlciBkb2VzKVxuICAgIFxuICAgIC8vIExpc3RlbiBmb3IgdGhlIGhlYWRsZXNzIHN0cmVhbSB0byBjb21wbGV0ZSBhbmQgc3RvcmUgaXRzIGVsZW1lbnRzXG4gICAgaGVhZGxlc3NSc2NIYW5kbGVyLnJzY1N0cmVhbS5vbignZW5kJywgKCkgPT4ge1xuICAgICAgaWYgKCFoYXNIZWFkbGVzc1N0cmVhbUVycm9yKGhlYWRsZXNzU3RyZWFtU3RhdGUsIGhhbmRsZXJPcHRpb25zLnJvdXRlKSkge1xuICAgICAgICBpZiAoaGFuZGxlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8uaW5mbyhcbiAgICAgICAgICAgIGBbcmVuZGVyUGFnZS5zZXJ2ZXJdIEhlYWRsZXNzIHN0cmVhbSBjb21wbGV0ZWQgc3VjY2Vzc2Z1bGx5IGZvciByb3V0ZSAke2hhbmRsZXJPcHRpb25zLnJvdXRlfWBcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG5cbiAgICBcbiAgICBpZiAoaGFuZGxlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy5pbmZvKFxuICAgICAgICBgW3JlbmRlclBhZ2Uuc2VydmVyXSBDcmVhdGVkIFBhZ2VDb21wb25lbnQgdGhhdCB1c2VzIFJlYWN0LnVzZSgpIHRvIGNvbnN1bWUgaGVhZGxlc3Mgc3RyZWFtIGZvciByb3V0ZSAke2hhbmRsZXJPcHRpb25zLnJvdXRlfWBcbiAgICAgICk7XG4gICAgfVxuICAgIFxuICAgIGZ1bGxSc2NIYW5kbGVyID0gcmVuZGVyUnNjU3RyZWFtKGZ1bGxSc2NIYW5kbGVyT3B0aW9ucywgZnVsbEhhbmRsZXJzKTtcbiAgICBcbiAgICAvLyBDaGVjayBmb3IgcGFuaWMgZXJyb3IgYWZ0ZXIgY3JlYXRpbmcgdGhlIGhhbmRsZXJcbiAgICBpZiAoZnVsbFBhbmljRXJyb3IpIHtcbiAgICAgIHlpZWxkIHtcbiAgICAgICAgdHlwZTogXCJlcnJvclwiLFxuICAgICAgICBlcnJvcjogZnVsbFBhbmljRXJyb3IsXG4gICAgICAgIG1ldHJpY3M6IHtcbiAgICAgICAgICByc2NGdWxsOiByc2NGdWxsTWV0cmljcyxcbiAgICAgICAgICByc2NIZWFkbGVzczogcnNjSGVhZGxlc3NNZXRyaWNzLFxuICAgICAgICAgIGh0bWw6IGh0bWxNZXRyaWNzLFxuICAgICAgICB9LFxuICAgICAgfTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBDcmVhdGUgSFRNTCB0cmFuc2Zvcm0gc3RyZWFtIC0gbmVlZCBjcmVhdGVSc2NUb0h0bWxTdHJlYW0gZm9yIGFzeW5jIHNlcnZlciBhY3Rpb25zXG4gICAgaHRtbFRyYW5zZm9ybVN0cmVhbSA9IGNyZWF0ZVJzY1RvSHRtbFN0cmVhbSh7XG4gICAgICBpZDogaGFuZGxlck9wdGlvbnMuaWQsXG4gICAgICB3b3JrZXI6IGhhbmRsZXJPcHRpb25zLndvcmtlcixcbiAgICAgIHJvdXRlOiBoYW5kbGVyT3B0aW9ucy5yb3V0ZSxcbiAgICAgIHVybDogaGFuZGxlck9wdGlvbnMudXJsLFxuICAgICAgbW9kdWxlUm9vdFBhdGg6IGhhbmRsZXJPcHRpb25zLm1vZHVsZVJvb3RQYXRoLFxuICAgICAgbW9kdWxlQmFzZVBhdGg6IGhhbmRsZXJPcHRpb25zLm1vZHVsZUJhc2VQYXRoLFxuICAgICAgbW9kdWxlQmFzZVVSTDogaGFuZGxlck9wdGlvbnMubW9kdWxlQmFzZVVSTCxcbiAgICAgIHByb2plY3RSb290OiBoYW5kbGVyT3B0aW9ucy5wcm9qZWN0Um9vdCxcbiAgICAgIGJ1aWxkOiBoYW5kbGVyT3B0aW9ucy5idWlsZCxcbiAgICAgIHBhbmljVGhyZXNob2xkOiBoYW5kbGVyT3B0aW9ucy5wYW5pY1RocmVzaG9sZCxcbiAgICAgIHZlcmJvc2U6IGhhbmRsZXJPcHRpb25zLnZlcmJvc2UsXG4gICAgICBzaWduYWw6IGhhbmRsZXJPcHRpb25zLnNpZ25hbCxcbiAgICAgIGxvZ2dlcjogaGFuZGxlck9wdGlvbnMubG9nZ2VyLFxuICAgICAgaHRtbFdvcmtlcjogaGFuZGxlck9wdGlvbnMuaHRtbFdvcmtlcixcbiAgICAgIGNsaWVudFBpcGVhYmxlU3RyZWFtT3B0aW9uczogaGFuZGxlck9wdGlvbnMuY2xpZW50UGlwZWFibGVTdHJlYW1PcHRpb25zLFxuICAgICAgb25NZXRyaWNzOiBoYW5kbGVyT3B0aW9ucy5vbk1ldHJpY3MsXG4gICAgICBodG1sVGltZW91dDogaGFuZGxlck9wdGlvbnMuaHRtbFRpbWVvdXQgfHwgMTUwMDAsXG4gICAgICByc2NTdHJlYW06IGZ1bGxSc2NIYW5kbGVyLnJzY1N0cmVhbSxcbiAgICAgIG9uRXJyb3I6IChlcnJvciwgaXNQYW5pYykgPT4ge1xuICAgICAgICAvLyBUcmFjayBIVE1MIHN0cmVhbSBlcnJvcnNcbiAgICAgICAgaHRtbFN0cmVhbUVycm9yZWQgPSB0cnVlO1xuICAgICAgICBodG1sU3RyZWFtRXJyb3IgPSBlcnJvcjtcbiAgICAgICAgXG4gICAgICAgIGlmIChpc1BhbmljKSB7XG4gICAgICAgICAgLy8gVGhpcyBpcyBhIHBhbmljIGVycm9yLCBpdCBzaG91bGQgYmUgeWllbGRlZCBhcyBhbiBlcnJvciByZXN1bHRcbiAgICAgICAgICBpZiAoaGFuZGxlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy5lcnJvcihcbiAgICAgICAgICAgICAgYFtyZW5kZXJQYWdlLnNlcnZlcl0gSFRNTCBzdHJlYW0gcGFuaWMgZXJyb3IgZm9yIHJvdXRlICR7aGFuZGxlck9wdGlvbnMucm91dGV9OiAke2Vycm9yLm1lc3NhZ2V9YFxuICAgICAgICAgICAgKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gRm9yIG5vbi1wYW5pYyBlcnJvcnMsIGp1c3QgbG9nIHRoZW1cbiAgICAgICAgICBpZiAoaGFuZGxlck9wdGlvbnMudmVyYm9zZSkge1xuICAgICAgICAgICAgaGFuZGxlck9wdGlvbnMubG9nZ2VyPy53YXJuKFxuICAgICAgICAgICAgICBgW3JlbmRlclBhZ2Uuc2VydmVyXSBIVE1MIHN0cmVhbSBlcnJvciBmb3Igcm91dGUgJHtoYW5kbGVyT3B0aW9ucy5yb3V0ZX06ICR7ZXJyb3IubWVzc2FnZX1gXG4gICAgICAgICAgICApO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIC8vIENyZWF0ZSBzdHJlYW0gd3JhcHBlcnMgZm9yIGZpbGUgd3JpdGluZyAtIHNpbXBsaWZpZWQgbGlrZSBjbGllbnQgc2lkZVxuICAgIGNvbnN0IHJzY1N0cmVhbVdyYXBwZXIgPSB7XG4gICAgICBwaXBlOiA8V3JpdGFibGUgZXh0ZW5kcyBOb2RlSlMuV3JpdGFibGVTdHJlYW0+KGRlc3RpbmF0aW9uOiBXcml0YWJsZSkgPT4ge1xuICAgICAgICBjb25zdCBzdHJlYW1NZXRyaWNzID0gY3JlYXRlU3RyZWFtTWV0cmljcygpO1xuICAgICAgICBzdHJlYW1NZXRyaWNzLnN0YXJ0VGltZSA9IHBlcmZvcm1hbmNlLm5vdygpO1xuXG4gICAgICAgIC8vIFVzZSB0aGUgaGVhZGxlc3MgUlNDIHN0cmVhbSBkaXJlY3RseSBmb3IgdGhlIC5yc2MgZmlsZVxuICAgICAgICBjb25zdCByc2NGaWxlU3RyZWFtID0gaGVhZGxlc3NSc2NIYW5kbGVyLnJzY1N0cmVhbTtcblxuICAgICAgICByc2NGaWxlU3RyZWFtLm9uKFwiZGF0YVwiLCAoY2h1bms6IEJ1ZmZlcikgPT4ge1xuICAgICAgICAgIHN0cmVhbU1ldHJpY3MuY2h1bmtzKys7XG4gICAgICAgICAgc3RyZWFtTWV0cmljcy5ieXRlcyArPSBjaHVuay5sZW5ndGg7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIHJzY0ZpbGVTdHJlYW0ub24oXCJlbmRcIiwgKCkgPT4ge1xuICAgICAgICAgIHN0cmVhbU1ldHJpY3MuZHVyYXRpb24gPSBwZXJmb3JtYW5jZS5ub3coKSAtIHN0cmVhbU1ldHJpY3Muc3RhcnRUaW1lO1xuICAgICAgICAgIHN0cmVhbU1ldHJpY3MuZW5kVGltZSA9IHBlcmZvcm1hbmNlLm5vdygpO1xuXG4gICAgICAgICAgcnNjSGVhZGxlc3NNZXRyaWNzLnN0cmVhbU1ldHJpY3MgPSBzdHJlYW1NZXRyaWNzO1xuICAgICAgICAgIHJzY0hlYWRsZXNzTWV0cmljcy5jaHVua1JhdGUgPSBzdHJlYW1NZXRyaWNzLmNodW5rcyAvIChzdHJlYW1NZXRyaWNzLmR1cmF0aW9uIC8gMTAwMCk7XG4gICAgICAgICAgcnNjSGVhZGxlc3NNZXRyaWNzLnByb2Nlc3NpbmdUaW1lID0gc3RyZWFtTWV0cmljcy5kdXJhdGlvbjtcbiAgICAgICAgICByc2NIZWFkbGVzc01ldHJpY3MubWVtb3J5VXNhZ2UgPSBwcm9jZXNzLm1lbW9yeVVzYWdlKCk7XG4gICAgICAgICAgcnNjSGVhZGxlc3NNZXRyaWNzLmNodW5rcyA9IHN0cmVhbU1ldHJpY3MuY2h1bmtzO1xuICAgICAgICB9KTtcblxuICAgICAgICByc2NGaWxlU3RyZWFtLnBpcGUoZGVzdGluYXRpb24pO1xuICAgICAgICByZXR1cm4gZGVzdGluYXRpb247XG4gICAgICB9LFxuICAgICAgYWJvcnQ6ICgpID0+IGhlYWRsZXNzUnNjSGFuZGxlci5hYm9ydCgpLFxuICAgIH07XG5cbiAgICBjb25zdCBodG1sU3RyZWFtV3JhcHBlciA9IHtcbiAgICAgIHBpcGU6IDxXcml0YWJsZSBleHRlbmRzIE5vZGVKUy5Xcml0YWJsZVN0cmVhbT4oZGVzdGluYXRpb246IFdyaXRhYmxlKSA9PiB7XG4gICAgICAgIC8vIFVzZSB0aGUgSFRNTCB0cmFuc2Zvcm0gc3RyZWFtJ3MgcGlwZSBtZXRob2QgZGlyZWN0bHkgKHNhbWUgYXMgY2xpZW50IHNpZGUpXG4gICAgICAgIHJldHVybiBodG1sVHJhbnNmb3JtU3RyZWFtLnBpcGUoZGVzdGluYXRpb24pO1xuICAgICAgfSxcbiAgICAgIGFib3J0OiAoKSA9PiB7XG4gICAgICAgIGh0bWxUcmFuc2Zvcm1TdHJlYW0uYWJvcnQoKTtcbiAgICAgIH0sXG4gICAgICBvbjogKGV2ZW50OiBzdHJpbmcsIGxpc3RlbmVyOiAoLi4uYXJnczogYW55W10pID0+IHZvaWQpID0+IHtcbiAgICAgICAgLy8gRm9yd2FyZCBlcnJvciBldmVudHMgZnJvbSB0aGUgSFRNTCB0cmFuc2Zvcm0gc3RyZWFtIHRvIHRoZSB3cmFwcGVyXG4gICAgICAgIGlmIChldmVudCA9PT0gJ2Vycm9yJykge1xuICAgICAgICAgIC8vIEFjY2VzcyB0aGUgYWN0dWFsIHN0cmVhbSBmcm9tIHRoZSB0cmFuc2Zvcm0gcmVzdWx0XG4gICAgICAgICAgY29uc3QgaHRtbFN0cmVhbSA9IChodG1sVHJhbnNmb3JtU3RyZWFtIGFzIGFueSkuaHRtbFN0cmVhbTtcbiAgICAgICAgICBpZiAoaHRtbFN0cmVhbSAmJiB0eXBlb2YgaHRtbFN0cmVhbS5vbiA9PT0gJ2Z1bmN0aW9uJykge1xuICAgICAgICAgICAgaHRtbFN0cmVhbS5vbignZXJyb3InLCBsaXN0ZW5lcik7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBodG1sU3RyZWFtV3JhcHBlcjtcbiAgICAgIH0sXG4gICAgfTtcblxuICAgIC8vIFdhaXQgZm9yIEhUTUwgc3RyZWFtIHRvIGNvbXBsZXRlIG9yIGVycm9yIGJlZm9yZSB5aWVsZGluZyBzdWNjZXNzXG4gICAgLy8gVGhpcyBlbnN1cmVzIHRoYXQgYW55IGVycm9ycyBmcm9tIHRoZSBIVE1MIHN0cmVhbSBhcmUgY2F1Z2h0IGJlZm9yZSB3ZSB5aWVsZCBzdWNjZXNzXG4gICAgYXdhaXQgbmV3IFByb21pc2U8dm9pZD4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgY29uc3QgdGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICByZWplY3QobmV3IEVycm9yKGBIVE1MIHN0cmVhbSB0aW1lb3V0IGZvciByb3V0ZSAke2hhbmRsZXJPcHRpb25zLnJvdXRlfWApKTtcbiAgICAgIH0sIGhhbmRsZXJPcHRpb25zLmh0bWxUaW1lb3V0IHx8IDE1MDAwKTtcblxuICAgICAgLy8gQ2hlY2sgaWYgSFRNTCBzdHJlYW0gYWxyZWFkeSBlcnJvcmVkXG4gICAgICBpZiAoaHRtbFN0cmVhbUVycm9yZWQpIHtcbiAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXQpO1xuICAgICAgICByZXNvbHZlKCk7IC8vIExldCB0aGUgcGFuaWMgdGhyZXNob2xkIGxvZ2ljIGhhbmRsZSB0aGlzIGF0IHRoZSByZW5kZXJQYWdlcyBsZXZlbFxuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIC8vIFNldCB1cCBhIGZsYWcgdG8gdHJhY2sgaWYgd2UndmUgcmVzb2x2ZWRcbiAgICAgIGxldCByZXNvbHZlZCA9IGZhbHNlO1xuICAgICAgXG4gICAgICAvLyBDcmVhdGUgYSB3cmFwcGVyIHRoYXQgcmVzb2x2ZXMgdGhlIHByb21pc2Ugd2hlbiB0aGUgc3RyZWFtIGNvbXBsZXRlc1xuICAgICAgY29uc3Qgb3JpZ2luYWxQaXBlID0gaHRtbFRyYW5zZm9ybVN0cmVhbS5waXBlO1xuICAgICAgaHRtbFRyYW5zZm9ybVN0cmVhbS5waXBlID0gZnVuY3Rpb24oZGVzdGluYXRpb246IGFueSkge1xuICAgICAgICBjb25zdCByZXN1bHQgPSBvcmlnaW5hbFBpcGUuY2FsbCh0aGlzLCBkZXN0aW5hdGlvbik7XG4gICAgICAgIFxuICAgICAgICAvLyBMaXN0ZW4gZm9yIHRoZSBkZXN0aW5hdGlvbiBzdHJlYW0gdG8gZW5kXG4gICAgICAgIGRlc3RpbmF0aW9uLm9uKCdmaW5pc2gnLCAoKSA9PiB7XG4gICAgICAgICAgaWYgKCFyZXNvbHZlZCkge1xuICAgICAgICAgICAgcmVzb2x2ZWQgPSB0cnVlO1xuICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXQpO1xuICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIFxuICAgICAgICBkZXN0aW5hdGlvbi5vbignZXJyb3InLCAoZXJyb3I6IEVycm9yKSA9PiB7XG4gICAgICAgICAgaWYgKCFyZXNvbHZlZCkge1xuICAgICAgICAgICAgcmVzb2x2ZWQgPSB0cnVlO1xuICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXQpO1xuICAgICAgICAgICAgcmVqZWN0KGVycm9yKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICBcbiAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICAgIH07XG4gICAgICBcbiAgICAgIC8vIElmIHdlIGRvbid0IGhhdmUgYSBkZXN0aW5hdGlvbiB5ZXQsIHJlc29sdmUgYWZ0ZXIgYSBzaG9ydCBkZWxheVxuICAgICAgLy8gVGhpcyBoYW5kbGVzIHRoZSBjYXNlIHdoZXJlIHRoZSBzdHJlYW0gaXMgY3JlYXRlZCBidXQgbm90IHlldCBwaXBlZFxuICAgICAgc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgIGlmICghcmVzb2x2ZWQpIHtcbiAgICAgICAgICByZXNvbHZlZCA9IHRydWU7XG4gICAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXQpO1xuICAgICAgICAgIHJlc29sdmUoKTtcbiAgICAgICAgfVxuICAgICAgfSwgMTAwKTtcbiAgICB9KTtcblxuICAgIC8vIENoZWNrIGZvciBIVE1MIHN0cmVhbSBlcnJvcnMgYWZ0ZXIgd2FpdGluZyBmb3IgY29tcGxldGlvblxuICAgIGlmIChodG1sU3RyZWFtRXJyb3JlZCkge1xuICAgICAgeWllbGQge1xuICAgICAgICB0eXBlOiBcImVycm9yXCIsXG4gICAgICAgIGVycm9yOiBodG1sU3RyZWFtRXJyb3IgfHwgbmV3IEVycm9yKFwiSFRNTCBzdHJlYW0gZmFpbGVkXCIpLFxuICAgICAgICBtZXRyaWNzOiB7XG4gICAgICAgICAgcnNjRnVsbDogcnNjRnVsbE1ldHJpY3MsXG4gICAgICAgICAgcnNjSGVhZGxlc3M6IHJzY0hlYWRsZXNzTWV0cmljcyxcbiAgICAgICAgICBodG1sOiBodG1sTWV0cmljcyxcbiAgICAgICAgfSxcbiAgICAgIH07XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gWWllbGQgc3VjY2VzcyByZXN1bHQgLSBzaW1wbGlmaWVkIGxpa2UgY2xpZW50IHNpZGVcbiAgICB5aWVsZCB7XG4gICAgICB0eXBlOiBcInN1Y2Nlc3NcIixcbiAgICAgIGh0bWw6IGh0bWxTdHJlYW1XcmFwcGVyLFxuICAgICAgcnNjOiByc2NTdHJlYW1XcmFwcGVyLFxuICAgICAgbWV0cmljczoge1xuICAgICAgICByc2NGdWxsOiByc2NGdWxsTWV0cmljcyxcbiAgICAgICAgcnNjSGVhZGxlc3M6IHJzY0hlYWRsZXNzTWV0cmljcyxcbiAgICAgICAgaHRtbDogaHRtbE1ldHJpY3MsXG4gICAgICB9LFxuICAgIH0gYXMgY29uc3Q7XG5cbiAgfSBjYXRjaCAoZXJyKSB7XG4gICAgaWYgKGhhbmRsZXJPcHRpb25zLnZlcmJvc2UpIHtcbiAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8uZXJyb3IoYFtyZW5kZXJQYWdlLnNlcnZlcl0gRXJyb3I6ICR7SlNPTi5zdHJpbmdpZnkoZXJyKX1gKTtcbiAgICB9XG5cbiAgICAvLyBDbGVhbiB1cCBhbnkgcmVzb3VyY2VzXG4gICAgdHJ5IHtcbiAgICAgIGlmIChoZWFkbGVzc1JzY0hhbmRsZXIpIGhlYWRsZXNzUnNjSGFuZGxlci5hYm9ydCgpO1xuICAgICAgaWYgKGZ1bGxSc2NIYW5kbGVyKSBmdWxsUnNjSGFuZGxlci5hYm9ydCgpO1xuICAgICAgaWYgKGh0bWxUcmFuc2Zvcm1TdHJlYW0pIGh0bWxUcmFuc2Zvcm1TdHJlYW0uYWJvcnQoKTtcbiAgICB9IGNhdGNoIChjbGVhbnVwRXJyb3I6IHVua25vd24pIHtcbiAgICAgIGhhbmRsZXJPcHRpb25zLmxvZ2dlcj8ud2FybihgRmFpbGVkIHRvIGNsZWFudXAgc3RyZWFtcyBvbiBlcnJvcjogJHtjbGVhbnVwRXJyb3J9YCk7XG4gICAgfVxuXG4gICAgY29uc3QgcGFuaWNFcnJvciA9IGhhbmRsZUVycm9yKHtcbiAgICAgIGVycm9yOiBlcnIsXG4gICAgICBjcml0aWNhbDogZmFsc2UsXG4gICAgICBsb2dnZXI6IGhhbmRsZXJPcHRpb25zLmxvZ2dlcixcbiAgICAgIHBhbmljVGhyZXNob2xkOiBoYW5kbGVyT3B0aW9ucy5wYW5pY1RocmVzaG9sZCxcbiAgICAgIGNvbnRleHQ6IGBSZW5kZXJQYWdlIEVycm9yICgke2hhbmRsZXJPcHRpb25zLnJvdXRlfSlgLFxuICAgIH0pO1xuXG4gICAgaWYgKHBhbmljRXJyb3IgIT0gbnVsbCkge1xuICAgICAgeWllbGQge1xuICAgICAgICB0eXBlOiBcImVycm9yXCIsXG4gICAgICAgIGVycm9yOiBwYW5pY0Vycm9yLFxuICAgICAgICBtZXRyaWNzOiB7XG4gICAgICAgICAgcnNjRnVsbDogcnNjRnVsbE1ldHJpY3MsXG4gICAgICAgICAgcnNjSGVhZGxlc3M6IHJzY0hlYWRsZXNzTWV0cmljcyxcbiAgICAgICAgICBodG1sOiBodG1sTWV0cmljcyxcbiAgICAgICAgfSxcbiAgICAgIH07XG4gICAgfSBlbHNlIHtcbiAgICAgIHlpZWxkIHtcbiAgICAgICAgdHlwZTogXCJza2lwXCIsXG4gICAgICAgIHJlYXNvbjogZXJyLFxuICAgICAgICBodG1sOiB7XG4gICAgICAgICAgcGlwZTogPFdyaXRhYmxlIGV4dGVuZHMgTm9kZUpTLldyaXRhYmxlU3RyZWFtPihkZXN0aW5hdGlvb