UNPKG

vite-plugin-react-server

Version:
510 lines (508 loc) 69 kB
/** * 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