UNPKG

@spartacus/setup

Version:

Includes features that makes Spartacus and it's setup easier and streamlined.

305 lines 43.7 kB
import * as fs from 'fs'; import { getRequestUrl } from '../util/request-url'; import { RenderingCache } from './rendering-cache'; import { RenderingStrategy, } from './ssr-optimization-options'; /** * Returns the full url for the given SSR Request. */ export const getDefaultRenderKey = getRequestUrl; /** * The rendered pages are kept in memory to be served on next request. If the `cache` is set to `false`, the * response is evicted as soon as the first successful response is successfully returned. */ export class OptimizedSsrEngine { constructor(expressEngine, ssrOptions) { this.expressEngine = expressEngine; this.ssrOptions = ssrOptions; this.currentConcurrency = 0; this.renderingCache = new RenderingCache(this.ssrOptions); this.templateCache = new Map(); /** * When the config `reuseCurrentRendering` is enabled, we want perform * only one render for one rendering key and reuse the html result * for all the pending requests for the same rendering key. * Therefore we need to store the callbacks for all the pending requests * and invoke them with the html after the render completes. * * This Map should be used only when `reuseCurrentRendering` config is enabled. * It's indexed by the rendering keys. */ this.renderCallbacks = new Map(); } get engineInstance() { return this.renderResponse.bind(this); } /** * When SSR page can not be returned in time, we're returning index.html of * the CSR application. * The CSR application is returned with the "Cache-Control: no-store" response-header. This notifies external cache systems to not use the CSR application for the subsequent request. */ fallbackToCsr(response, filePath, callback) { response.set('Cache-Control', 'no-store'); callback(undefined, this.getDocument(filePath)); } getRenderingKey(request) { var _a; return ((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.renderKeyResolver) ? this.ssrOptions.renderKeyResolver(request) : getDefaultRenderKey(request); } getRenderingStrategy(request) { var _a; return ((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.renderingStrategyResolver) ? this.ssrOptions.renderingStrategyResolver(request) : RenderingStrategy.DEFAULT; } /** * When returns true, the server side rendering should be performed. * When returns false, the CSR fallback should be returned. * * We should not render, when there is already * a pending rendering for the same rendering key * (unless the `reuseCurrentRendering` config option is enabled) * OR when the concurrency limit is exceeded. */ shouldRender(request) { var _a, _b; const renderingKey = this.getRenderingKey(request); const concurrencyLimitExceeded = this.isConcurrencyLimitExceeded(renderingKey); const fallBack = this.renderingCache.isRendering(renderingKey) && !((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.reuseCurrentRendering); if (fallBack) { this.log(`CSR fallback: rendering in progress (${request === null || request === void 0 ? void 0 : request.originalUrl})`); } else if (concurrencyLimitExceeded) { this.log(`CSR fallback: Concurrency limit exceeded (${(_b = this.ssrOptions) === null || _b === void 0 ? void 0 : _b.concurrency})`); } return ((!fallBack && !concurrencyLimitExceeded && this.getRenderingStrategy(request) !== RenderingStrategy.ALWAYS_CSR) || this.getRenderingStrategy(request) === RenderingStrategy.ALWAYS_SSR); } /** * Checks for the concurrency limit * * @returns true if rendering this request would exceed the concurrency limit */ isConcurrencyLimitExceeded(renderingKey) { var _a, _b; // If we can reuse a pending render for this request, we don't take up a new concurrency slot. // In that case we don't exceed the concurrency limit even if the `currentConcurrency` // already reaches the limit. if (((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.reuseCurrentRendering) && this.renderingCache.isRendering(renderingKey)) { return false; } return ((_b = this.ssrOptions) === null || _b === void 0 ? void 0 : _b.concurrency) ? this.currentConcurrency >= this.ssrOptions.concurrency : false; } /** * Returns true, when the `timeout` option has been configured to non-zero value OR * when the rendering strategy for the given request is ALWAYS_SSR. * Otherwise, it returns false. */ shouldTimeout(request) { var _a; return (!!((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.timeout) || this.getRenderingStrategy(request) === RenderingStrategy.ALWAYS_SSR); } /** * Returns the timeout value. * * In case of the rendering strategy ALWAYS_SSR, it returns the config `forcedSsrTimeout`. * Otherwise, it returns the config `timeout`. */ getTimeout(request) { var _a, _b, _c, _d; return this.getRenderingStrategy(request) === RenderingStrategy.ALWAYS_SSR ? (_b = (_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.forcedSsrTimeout) !== null && _b !== void 0 ? _b : 60000 : (_d = (_c = this.ssrOptions) === null || _c === void 0 ? void 0 : _c.timeout) !== null && _d !== void 0 ? _d : 0; } /** * If there is an available cached response for this rendering key, * it invokes the given render callback with the response and returns true. * * Otherwise, it returns false. */ returnCachedRender(request, callback) { var _a; const key = this.getRenderingKey(request); if (this.renderingCache.isReady(key)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const cached = this.renderingCache.get(key); callback(cached.err, cached.html); if (!((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.cache)) { // we drop cached rendering if caching is disabled this.renderingCache.clear(key); } return true; } return false; } /** * Handles the request and invokes the given `callback` with the result html / error. * * The result might be ether: * - a CSR fallback with a basic `index.html` content * - a result rendered by the original Angular Universal express engine * - a result from the in-memory cache (which was previously rendered by Angular Universal express engine). */ renderResponse(filePath, options, callback) { const request = options.req; const response = options.res || options.req.res; if (this.returnCachedRender(request, callback)) { this.log(`Render from cache (${request === null || request === void 0 ? void 0 : request.originalUrl})`); return; } if (!this.shouldRender(request)) { this.fallbackToCsr(response, filePath, callback); return; } let requestTimeout; if (this.shouldTimeout(request)) { // establish timeout for rendering const timeout = this.getTimeout(request); requestTimeout = setTimeout(() => { requestTimeout = undefined; this.fallbackToCsr(response, filePath, callback); this.log(`SSR rendering exceeded timeout ${timeout}, fallbacking to CSR for ${request === null || request === void 0 ? void 0 : request.originalUrl}`, false); }, timeout); } else { // Here we respond with the fallback to CSR, but we don't `return`. // We let the actual rendering task to happen in the background // to eventually store the rendered result in the cache. this.fallbackToCsr(response, filePath, callback); } const renderingKey = this.getRenderingKey(request); const renderCallback = (err, html) => { var _a; if (requestTimeout) { // if request is still waiting for render, return it clearTimeout(requestTimeout); callback(err, html); this.log(`Request is resolved with the SSR rendering result (${request === null || request === void 0 ? void 0 : request.originalUrl})`); // store the render only if caching is enabled if ((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.cache) { this.renderingCache.store(renderingKey, err, html); } else { this.renderingCache.clear(renderingKey); } } else { // store the render for future use this.renderingCache.store(renderingKey, err, html); } }; this.handleRender({ filePath, options, renderCallback, request, }); } log(message, debug = true) { var _a; if (!debug || ((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.debug)) { console.log(message); } } /** Retrieve the document from the cache or the filesystem */ getDocument(filePath) { let doc = this.templateCache.get(filePath); if (!doc) { // fs.readFileSync could be missing in a browser, specifically // in a unit tests with { node: { fs: 'empty' } } webpack configuration doc = (fs === null || fs === void 0 ? void 0 : fs.readFileSync) ? fs.readFileSync(filePath, 'utf-8') : ''; this.templateCache.set(filePath, doc); } return doc; } /** * Delegates the render to the original _Angular Universal express engine_. * * In case when the config `reuseCurrentRendering` is enabled and **if there is already a pending * render task for the same rendering key**, it doesn't delegate a new render to Angular Universal. * Instead, it waits for the current rendering to complete and then reuse the result for all waiting requests. */ handleRender({ filePath, options, renderCallback, request, }) { var _a, _b; if (!((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.reuseCurrentRendering)) { this.startRender({ filePath, options, renderCallback, request, }); return; } const renderingKey = this.getRenderingKey(request); if (!this.renderCallbacks.has(renderingKey)) { this.renderCallbacks.set(renderingKey, []); } (_b = this.renderCallbacks.get(renderingKey)) === null || _b === void 0 ? void 0 : _b.push(renderCallback); if (!this.renderingCache.isRendering(renderingKey)) { this.startRender({ filePath, options, request, renderCallback: (err, html) => { // Share the result of the render with all awaiting requests for the same key: var _a; // Note: we access the Map at the moment of the render finished (don't store value in a local variable), // because in the meantime something might have deleted the value (i.e. when `maxRenderTime` passed). (_a = this.renderCallbacks .get(renderingKey)) === null || _a === void 0 ? void 0 : _a.forEach((cb) => cb(err, html)); // pass the shared result to all waiting rendering callbacks this.renderCallbacks.delete(renderingKey); }, }); } this.log(`Request is waiting for the SSR rendering to complete (${request === null || request === void 0 ? void 0 : request.originalUrl})`); } /** * Delegates the render to the original _Angular Universal express engine_. * * There is no way to abort the running render of Angular Universal. * So if the render doesn't complete in the configured `maxRenderTime`, * we just consider the render task as hanging (note: it's a potential memory leak!). * Later on, even if the render completes somewhen in the future, we will ignore * its result. */ startRender({ filePath, options, renderCallback, request, }) { var _a, _b; const renderingKey = this.getRenderingKey(request); // Setting the timeout for hanging renders that might not ever finish due to various reasons. // After the configured `maxRenderTime` passes, we consider the rendering task as hanging, // and release the concurrency slot and forget all callbacks waiting for the render's result. let maxRenderTimeout = setTimeout(() => { var _a; this.renderingCache.clear(renderingKey); maxRenderTimeout = undefined; this.currentConcurrency--; if ((_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.reuseCurrentRendering) { this.renderCallbacks.delete(renderingKey); } this.log(`Rendering of ${request === null || request === void 0 ? void 0 : request.originalUrl} was not able to complete. This might cause memory leaks!`, false); }, (_b = (_a = this.ssrOptions) === null || _a === void 0 ? void 0 : _a.maxRenderTime) !== null && _b !== void 0 ? _b : 300000); // 300000ms == 5 minutes this.log(`Rendering started (${request === null || request === void 0 ? void 0 : request.originalUrl})`); this.renderingCache.setAsRendering(renderingKey); this.currentConcurrency++; this.expressEngine(filePath, options, (err, html) => { if (!maxRenderTimeout) { // ignore this render's result because it exceeded maxRenderTimeout this.log(`Rendering of ${request.originalUrl} completed after the specified maxRenderTime, therefore it was ignored.`, false); return; } clearTimeout(maxRenderTimeout); this.log(`Rendering completed (${request === null || request === void 0 ? void 0 : request.originalUrl})`); this.currentConcurrency--; renderCallback(err, html); }); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3B0aW1pemVkLXNzci1lbmdpbmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9jb3JlLWxpYnMvc2V0dXAvc3NyL29wdGltaXplZC1lbmdpbmUvb3B0aW1pemVkLXNzci1lbmdpbmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFFekIsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3BELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUNuRCxPQUFPLEVBQ0wsaUJBQWlCLEdBRWxCLE1BQU0sNEJBQTRCLENBQUM7QUFFcEM7O0dBRUc7QUFDSCxNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxhQUFhLENBQUM7QUFhakQ7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLGtCQUFrQjtJQXFCN0IsWUFDWSxhQUFzQyxFQUN0QyxVQUFtQztRQURuQyxrQkFBYSxHQUFiLGFBQWEsQ0FBeUI7UUFDdEMsZUFBVSxHQUFWLFVBQVUsQ0FBeUI7UUF0QnJDLHVCQUFrQixHQUFHLENBQUMsQ0FBQztRQUN2QixtQkFBYyxHQUFHLElBQUksY0FBYyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUN2RCxrQkFBYSxHQUFHLElBQUksR0FBRyxFQUFrQixDQUFDO1FBRWxEOzs7Ozs7Ozs7V0FTRztRQUNLLG9CQUFlLEdBQUcsSUFBSSxHQUFHLEVBQTJCLENBQUM7SUFTMUQsQ0FBQztJQVBKLElBQUksY0FBYztRQUNoQixPQUFPLElBQUksQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3hDLENBQUM7SUFPRDs7OztPQUlHO0lBQ08sYUFBYSxDQUNyQixRQUFrQixFQUNsQixRQUFnQixFQUNoQixRQUF1QjtRQUV2QixRQUFRLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxVQUFVLENBQUMsQ0FBQztR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R0FBRyxDQUFDLHNCQUFzQixPQUFPLGFBQVAsT0FBTyx1QkFBUCxPQUFPLENBQUUsV0FBVyxHQUFHLENBQUMsQ0FBQztRQUN4RCxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUNqRCxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUUxQixJQUFJLENBQUMsYUFBYSxDQUFDLFFBQVEsRUFBRSxPQUFPLEVBQUUsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUU7WUFDbEQsSUFBSSxDQUFDLGdCQUFnQixFQUFFO2dCQUNyQixtRUFBbUU7Z0JBQ25FLElBQUksQ0FBQyxHQUFHLENBQ04sZ0JBQWdCLE9BQU8sQ0FBQyxXQUFXLHlFQUF5RSxFQUM1RyxLQUFLLENBQ04sQ0FBQztnQkFDRixPQUFPO2FBQ1I7WUFDRCxZQUFZLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztZQUUvQixJQUFJLENBQUMsR0FBRyxDQUFDLHdCQUF3QixPQUFPLGFBQVAsT0FBTyx1QkFBUCxPQUFPLENBQUUsV0FBVyxHQUFHLENBQUMsQ0FBQztZQUMxRCxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUUxQixjQUFjLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzVCLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyogd2VicGFja0lnbm9yZTogdHJ1ZSAqL1xuaW1wb3J0IHsgUmVxdWVzdCwgUmVzcG9uc2UgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzJztcbmltcG9ydCB7IE5nRXhwcmVzc0VuZ2luZUluc3RhbmNlIH0gZnJvbSAnLi4vZW5naW5lLWRlY29yYXRvci9uZy1leHByZXNzLWVuZ2luZS1kZWNvcmF0b3InO1xuaW1wb3J0IHsgZ2V0UmVxdWVzdFVybCB9IGZyb20gJy4uL3V0aWwvcmVxdWVzdC11cmwnO1xuaW1wb3J0IHsgUmVuZGVyaW5nQ2FjaGUgfSBmcm9tICcuL3JlbmRlcmluZy1jYWNoZSc7XG5pbXBvcnQge1xuICBSZW5kZXJpbmdTdHJhdGVneSxcbiAgU3NyT3B0aW1pemF0aW9uT3B0aW9ucyxcbn0gZnJvbSAnLi9zc3Itb3B0aW1pemF0aW9uLW9wdGlvbnMnO1xuXG4vKipcbiAqIFJldHVybnMgdGhlIGZ1bGwgdXJsIGZvciB0aGUgZ2l2ZW4gU1NSIFJlcXVlc3QuXG4gKi9cbmV4cG9ydCBjb25zdCBnZXREZWZhdWx0UmVuZGVyS2V5ID0gZ2V0UmVxdWVzdFVybDtcblxuZXhwb3J0IHR5cGUgU3NyQ2FsbGJhY2tGbiA9IChcbiAgLyoqXG4gICAqIEVycm9yIHRoYXQgbWlnaHQndmUgb2NjdXJyZWQgd2hpbGUgcmVuZGVyaW5nLlxuICAgKi9cbiAgZXJyPzogRXJyb3IgfCBudWxsIHwgdW5kZWZpbmVkLFxuICAvKipcbiAgICogSFRNTCByZXNwb25zZS5cbiAgICovXG4gIGh0bWw/OiBzdHJpbmcgfCB1bmRlZmluZWRcbikgPT4gdm9pZDtcblxuLyoqXG4gKiBUaGUgcmVuZGVyZWQgcGFnZXMgYXJlIGtlcHQgaW4gbWVtb3J5IHRvIGJlIHNlcnZlZCBvbiBuZXh0IHJlcXVlc3QuIElmIHRoZSBgY2FjaGVgIGlzIHNldCB0byBgZmFsc2VgLCB0aGVcbiAqIHJlc3BvbnNlIGlzIGV2aWN0ZWQgYXMgc29vbiBhcyB0aGUgZmlyc3Qgc3VjY2Vzc2Z1bCByZXNwb25zZSBpcyBzdWNjZXNzZnVsbHkgcmV0dXJuZWQuXG4gKi9cbmV4cG9ydCBjbGFzcyBPcHRpbWl6ZWRTc3JFbmdpbmUge1xuICBwcm90ZWN0ZWQgY3VycmVudENvbmN1cnJlbmN5ID0gMDtcbiAgcHJvdGVjdGVkIHJlbmRlcmluZ0NhY2hlID0gbmV3IFJlbmRlcmluZ0NhY2hlKHRoaXMuc3NyT3B0aW9ucyk7XG4gIHByaXZhdGUgdGVtcGxhdGVDYWNoZSA9IG5ldyBNYXA8c3RyaW5nLCBzdHJpbmc+KCk7XG5cbiAgLyoqXG4gICAqIFdoZW4gdGhlIGNvbmZpZyBgcmV1c2VDdXJyZW50UmVuZGVyaW5nYCBpcyBlbmFibGVkLCB3ZSB3YW50IHBlcmZvcm1cbiAgICogb25seSBvbmUgcmVuZGVyIGZvciBvbmUgcmVuZGVyaW5nIGtleSBhbmQgcmV1c2UgdGhlIGh0bWwgcmVzdWx0XG4gICAqIGZvciBhbGwgdGhlIHBlbmRpbmcgcmVxdWVzdHMgZm9yIHRoZSBzYW1lIHJlbmRlcmluZyBrZXkuXG4gICAqIFRoZXJlZm9yZSB3ZSBuZWVkIHRvIHN0b3JlIHRoZSBjYWxsYmFja3MgZm9yIGFsbCB0aGUgcGVuZGluZyByZXF1ZXN0c1xuICAgKiBhbmQgaW52b2tlIHRoZW0gd2l0aCB0aGUgaHRtbCBhZnRlciB0aGUgcmVuZGVyIGNvbXBsZXRlcy5cbiAgICpcbiAgICogVGhpcyBNYXAgc2hvdWxkIGJlIHVzZWQgb25seSB3aGVuIGByZXVzZUN1cnJlbnRSZW5kZXJpbmdgIGNvbmZpZyBpcyBlbmFibGVkLlxuICAgKiBJdCdzIGluZGV4ZWQgYnkgdGhlIHJlbmRlcmluZyBrZXlzLlxuICAgKi9cbiAgcHJpdmF0ZSByZW5kZXJDYWxsYmFja3MgPSBuZXcgTWFwPHN0cmluZywgU3NyQ2FsbGJhY2tGbltdPigpO1xuXG4gIGdldCBlbmdpbmVJbnN0YW5jZSgpOiBOZ0V4cHJlc3NFbmdpbmVJbnN0YW5jZSB7XG4gICAgcmV0dXJuIHRoaXMucmVuZGVyUmVzcG9uc2UuYmluZCh0aGlzKTtcbiAgfVxuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByb3RlY3RlZCBleHByZXNzRW5naW5lOiBOZ0V4cHJlc3NFbmdpbmVJbnN0YW5jZSxcbiAgICBwcm90ZWN0ZWQgc3NyT3B0aW9ucz86IFNzck9wdGltaXphdGlvbk9wdGlvbnNcbiAgKSB7fVxuXG4gIC8qKlxuICAgKiBXaGVuIFNTUiBwYWdlIGNhbiBub3QgYmUgcmV0dXJuZWQgaW4gdGltZSwgd2UncmUgcmV0dXJuaW5nIGluZGV4Lmh0bWwgb2ZcbiAgICogdGhlIENTUiBhcHBsaWNhdGlvbi5cbiAgICogVGhlIENTUiBhcHBsaWNhdGlvbiBpcyByZXR1cm5lZCB3aXRoIHRoZSBcIkNhY2hlLUNvbnRyb2w6IG5vLXN0b3JlXCIgcmVzcG9uc2UtaGVhZGVyLiBUaGlzIG5vdGlmaWVzIGV4dGVybmFsIGNhY2hlIHN5c3RlbXMgdG8gbm90IHVzZSB0aGUgQ1NSIGFwcGxpY2F0aW9uIGZvciB0aGUgc3Vic2VxdWVudCByZXF1ZXN0LlxuICAgKi9cbiAgcHJvdGVjdGVkIGZhbGxiYWNrVG9Dc3IoXG4gICAgcmVzcG9uc2U6IFJlc3BvbnNlLFxuICAgIGZpbGVQYXRoOiBzdHJpbmcsXG4gICAgY2FsbGJhY2s6IFNzckNhbGxiYWNrRm5cbiAgKTogdm9pZCB7XG4gICAgcmVzcG9uc2Uuc2V0KCdDYWNoZS1Db250cm9sJywgJ25vLXN0b3JlJyk7XG4gICAgY2FsbGJhY2sodW5kZWZpbmVkLCB0aGlzLmdldERvY3VtZW50KGZpbGVQYXRoKSk7XG4gIH1cblxuICBwcm90ZWN0ZWQgZ2V0UmVuZGVyaW5nS2V5KHJlcXVlc3Q6IFJlcXVlc3QpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLnNzck9wdGlvbnM/LnJlbmRlcktleVJlc29sdmVyXG4gICAgICA/IHRoaXMuc3NyT3B0aW9ucy5yZW5kZXJLZXlSZXNvbHZlcihyZXF1ZXN0KVxuICAgICAgOiBnZXREZWZhdWx0UmVuZGVyS2V5KHJlcXVlc3QpO1xuICB9XG5cbiAgcHJvdGVjdGVkIGdldFJlbmRlcmluZ1N0cmF0ZWd5KHJlcXVlc3Q6IFJlcXVlc3QpOiBSZW5kZXJpbmdTdHJhdGVneSB7XG4gICAgcmV0dXJuIHRoaXMuc3NyT3B0aW9ucz8ucmVuZGVyaW5nU3RyYXRlZ3lSZXNvbHZlclxuICAgICAgPyB0aGlzLnNzck9wdGlvbnMucmVuZGVyaW5nU3RyYXRlZ3lSZXNvbHZlcihyZXF1ZXN0KVxuICAgICAgOiBSZW5kZXJpbmdTdHJhdGVneS5ERUZBVUxUO1xuICB9XG5cbiAgLyoqXG4gICAqIFdoZW4gcmV0dXJucyB0cnVlLCB0aGUgc2VydmVyIHNpZGUgcmVuZGVyaW5nIHNob3VsZCBiZSBwZXJmb3JtZWQuXG4gICAqIFdoZW4gcmV0dXJucyBmYWxzZSwgdGhlIENTUiBmYWxsYmFjayBzaG91bGQgYmUgcmV0dXJuZWQuXG4gICAqXG4gICAqIFdlIHNob3VsZCBub3QgcmVuZGVyLCB3aGVuIHRoZXJlIGlzIGFscmVhZHlcbiAgICogYSBwZW5kaW5nIHJlbmRlcmluZyBmb3IgdGhlIHNhbWUgcmVuZGVyaW5nIGtleVxuICAgKiAodW5sZXNzIHRoZSBgcmV1c2VDdXJyZW50UmVuZGVyaW5nYCBjb25maWcgb3B0aW9uIGlzIGVuYWJsZWQpXG4gICAqIE9SIHdoZW4gdGhlIGNvbmN1cnJlbmN5IGxpbWl0IGlzIGV4Y2VlZGVkLlxuICAgKi9cbiAgcHJvdGVjdGVkIHNob3VsZFJlbmRlcihyZXF1ZXN0OiBSZXF1ZXN0KTogYm9vbGVhbiB7XG4gICAgY29uc3QgcmVuZGVyaW5nS2V5ID0gdGhpcy5nZXRSZW5kZXJpbmdLZXkocmVxdWVzdCk7XG4gICAgY29uc3QgY29uY3VycmVuY3lMaW1pdEV4Y2VlZGVkID1cbiAgICAgIHRoaXMuaXNDb25jdXJyZW5jeUxpbWl0RXhjZWVkZWQocmVuZGVyaW5nS2V5KTtcbiAgICBjb25zdCBmYWxsQmFjayA9XG4gICAgICB0aGlzLnJlbmRlcmluZ0NhY2hlLmlzUmVuZGVyaW5nKHJlbmRlcmluZ0tleSkgJiZcbiAgICAgICF0aGlzLnNzck9wdGlvbnM/LnJldXNlQ3VycmVudFJlbmRlcmluZztcblxuICAgIGlmIChmYWxsQmFjaykge1xuICAgICAgdGhpcy5sb2coYENTUiBmYWxsYmFjazogcmVuZGVyaW5nIGluIHByb2dyZXNzICgke3JlcXVlc3Q/Lm9yaWdpbmFsVXJsfSlgKTtcbiAgICB9IGVsc2UgaWYgKGNvbmN1cnJlbmN5TGltaXRFeGNlZWRlZCkge1xuICAgICAgdGhpcy5sb2coXG4gICAgICAgIGBDU1IgZmFsbGJhY2s6IENvbmN1cnJlbmN5IGxpbWl0IGV4Y2VlZGVkICgke3RoaXMuc3NyT3B0aW9ucz8uY29uY3VycmVuY3l9KWBcbiAgICAgICk7XG4gICAgfVxuXG4gICAgcmV0dXJuIChcbiAgICAgICghZmFsbEJhY2sgJiZcbiAgICAgICAgIWNvbmN1cnJlbmN5TGltaXRFeGNlZWRlZCAmJlxuICAgICAgICB0aGlzLmdldFJlbmRlcmluZ1N0cmF0ZWd5KHJlcXVlc3QpICE9PSBSZW5kZXJpbmdTdHJhdGVneS5BTFdBWVNfQ1NSKSB8fFxuICAgICAgdGhpcy5nZXRSZW5kZXJpbmdTdHJhdGVneShyZXF1ZXN0KSA9PT0gUmVuZGVyaW5nU3RyYXRlZ3kuQUxXQVlTX1NTUlxuICAgICk7XG4gIH1cblxuICAvKipcbiAgICogQ2hlY2tzIGZvciB0aGUgY29uY3VycmVuY3kgbGltaXRcbiAgICpcbiAgICogQHJldHVybnMgdHJ1ZSBpZiByZW5kZXJpbmcgdGhpcyByZXF1ZXN0IHdvdWxkIGV4Y2VlZCB0aGUgY29uY3VycmVuY3kgbGltaXRcbiAgICovXG4gIHByaXZhdGUgaXNDb25jdXJyZW5jeUxpbWl0RXhjZWVkZWQocmVuZGVyaW5nS2V5OiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICAvLyBJZiB3ZSBjYW4gcmV1c2UgYSBwZW5kaW5nIHJlbmRlciBmb3IgdGhpcyByZXF1ZXN0LCB3ZSBkb24ndCB0YWtlIHVwIGEgbmV3IGNvbmN1cnJlbmN5IHNsb3QuXG4gICAgLy8gSW4gdGhhdCBjYXNlIHdlIGRvbid0IGV4Y2VlZCB0aGUgY29uY3VycmVuY3kgbGltaXQgZXZlbiBpZiB0aGUgYGN1cnJlbnRDb25jdXJyZW5jeWBcbiAgICAvLyBhbHJlYWR5IHJlYWNoZXMgdGhlIGxpbWl0LlxuICAgIGlmIChcbiAgICAgIHRoaXMuc3NyT3B0aW9ucz8ucmV1c2VDdXJyZW50UmVuZGVyaW5nICYmXG4gICAgICB0aGlzLnJlbmRlcmluZ0NhY2hlLmlzUmVuZGVyaW5nKHJlbmRlcmluZ0tleSlcbiAgICApIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5zc3JPcHRpb25zPy5jb25jdXJyZW5jeVxuICAgICAgPyB0aGlzLmN1cnJlbnRDb25jdXJyZW5jeSA+PSB0aGlzLnNzck9wdGlvbnMuY29uY3VycmVuY3lcbiAgICAgIDogZmFsc2U7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0cnVlLCB3aGVuIHRoZSBgdGltZW91dGAgb3B0aW9uIGhhcyBiZWVuIGNvbmZpZ3VyZWQgdG8gbm9uLXplcm8gdmFsdWUgT1JcbiAgICogd2hlbiB0aGUgcmVuZGVyaW5nIHN0cmF0ZWd5IGZvciB0aGUgZ2l2ZW4gcmVxdWVzdCBpcyBBTFdBWVNfU1NSLlxuICAgKiBPdGhlcndpc2UsIGl0IHJldHVybnMgZmFsc2UuXG4gICAqL1xuICBwcm90ZWN0ZWQgc2hvdWxkVGltZW91dChyZXF1ZXN0OiBSZXF1ZXN0KTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIChcbiAgICAgICEhdGhpcy5zc3JPcHRpb25zPy50aW1lb3V0IHx8XG4gICAgICB0aGlzLmdldFJlbmRlcmluZ1N0cmF0ZWd5KHJlcXVlc3QpID09PSBSZW5kZXJpbmdTdHJhdGVneS5BTFdBWVNfU1NSXG4gICAgKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSB0aW1lb3V0IHZhbHVlLlxuICAgKlxuICAgKiBJbiBjYXNlIG9mIHRoZSByZW5kZXJpbmcgc3RyYXRlZ3kgQUxXQVlTX1NTUiwgaXQgcmV0dXJucyB0aGUgY29uZmlnIGBmb3JjZWRTc3JUaW1lb3V0YC5cbiAgICogT3RoZXJ3aXNlLCBpdCByZXR1cm5zIHRoZSBjb25maWcgYHRpbWVvdXRgLlxuICAgKi9cbiAgcHJvdGVjdGVkIGdldFRpbWVvdXQocmVxdWVzdDogUmVxdWVzdCk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0UmVuZGVyaW5nU3RyYXRlZ3kocmVxdWVzdCkgPT09IFJlbmRlcmluZ1N0cmF0ZWd5LkFMV0FZU19TU1JcbiAgICAgID8gdGhpcy5zc3JPcHRpb25zPy5mb3JjZWRTc3JUaW1lb3V0ID8/IDYwMDAwXG4gICAgICA6IHRoaXMuc3NyT3B0aW9ucz8udGltZW91dCA/PyAwO1xuICB9XG5cbiAgLyoqXG4gICAqIElmIHRoZXJlIGlzIGFuIGF2YWlsYWJsZSBjYWNoZWQgcmVzcG9uc2UgZm9yIHRoaXMgcmVuZGVyaW5nIGtleSxcbiAgICogaXQgaW52b2tlcyB0aGUgZ2l2ZW4gcmVuZGVyIGNhbGxiYWNrIHdpdGggdGhlIHJlc3BvbnNlIGFuZCByZXR1cm5zIHRydWUuXG4gICAqXG4gICAqIE90aGVyd2lzZSwgaXQgcmV0dXJucyBmYWxzZS5cbiAgICovXG4gIHByb3RlY3RlZCByZXR1cm5DYWNoZWRSZW5kZXIoXG4gICAgcmVxdWVzdDogUmVxdWVzdCxcbiAgICBjYWxsYmFjazogU3NyQ2FsbGJhY2tGblxuICApOiBib29sZWFuIHtcbiAgICBjb25zdCBrZXkgPSB0aGlzLmdldFJlbmRlcmluZ0tleShyZXF1ZXN0KTtcblxuICAgIGlmICh0aGlzLnJlbmRlcmluZ0NhY2hlLmlzUmVhZHkoa2V5KSkge1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1ub24tbnVsbC1hc3NlcnRpb25cbiAgICAgIGNvbnN0IGNhY2hlZCA9IHRoaXMucmVuZGVyaW5nQ2FjaGUuZ2V0KGtleSkhO1xuICAgICAgY2FsbGJhY2soY2FjaGVkLmVyciwgY2FjaGVkLmh0bWwpO1xuXG4gICAgICBpZiAoIXRoaXMuc3NyT3B0aW9ucz8uY2FjaGUpIHtcbiAgICAgICAgLy8gd2UgZHJvcCBjYWNoZWQgcmVuZGVyaW5nIGlmIGNhY2hpbmcgaXMgZGlzYWJsZWRcbiAgICAgICAgdGhpcy5yZW5kZXJpbmdDYWNoZS5jbGVhcihrZXkpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBIYW5kbGVzIHRoZSByZXF1ZXN0IGFuZCBpbnZva2VzIHRoZSBnaXZlbiBgY2FsbGJhY2tgIHdpdGggdGhlIHJlc3VsdCBodG1sIC8gZXJyb3IuXG4gICAqXG4gICAqIFRoZSByZXN1bHQgbWlnaHQgYmUgZXRoZXI6XG4gICAqIC0gYSBDU1IgZmFsbGJhY2sgd2l0aCBhIGJhc2ljIGBpbmRleC5odG1sYCBjb250ZW50XG4gICAqIC0gYSByZXN1bHQgcmVuZGVyZWQgYnkgdGhlIG9yaWdpbmFsIEFuZ3VsYXIgVW5pdmVyc2FsIGV4cHJlc3MgZW5naW5lXG4gICAqIC0gYSByZXN1bHQgZnJvbSB0aGUgaW4tbWVtb3J5IGNhY2hlICh3aGljaCB3YXMgcHJldmlvdXNseSByZW5kZXJlZCBieSBBbmd1bGFyIFVuaXZlcnNhbCBleHByZXNzIGVuZ2luZSkuXG4gICAqL1xuICBwcm90ZWN0ZWQgcmVuZGVyUmVzcG9uc2UoXG4gICAgZmlsZVBhdGg6IHN0cmluZyxcbiAgICBvcHRpb25zOiBhbnksXG4gICAgY2FsbGJhY2s6IFNzckNhbGxiYWNrRm5cbiAgKTogdm9pZCB7XG4gICAgY29uc3QgcmVxdWVzdDogUmVxdWVzdCA9IG9wdGlvbnMucmVxO1xuICAgIGNvbnN0IHJlc3BvbnNlOiBSZXNwb25zZSA9IG9wdGlvbnMucmVzIHx8IG9wdGlvbnMucmVxLnJlcztcblxuICAgIGlmICh0aGlzLnJldHVybkNhY2hlZFJlbmRlcihyZXF1ZXN0LCBjYWxsYmFjaykpIHtcbiAgICAgIHRoaXMubG9nKGBSZW5kZXIgZnJvbSBjYWNoZSAoJHtyZXF1ZXN0Py5vcmlnaW5hbFVybH0pYCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmICghdGhpcy5zaG91bGRSZW5kZXIocmVxdWVzdCkpIHtcbiAgICAgIHRoaXMuZmFsbGJhY2tUb0NzcihyZXNwb25zZSwgZmlsZVBhdGgsIGNhbGxiYWNrKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBsZXQgcmVxdWVzdFRpbWVvdXQ6IE5vZGVKUy5UaW1lb3V0IHwgdW5kZWZpbmVkO1xuICAgIGlmICh0aGlzLnNob3VsZFRpbWVvdXQocmVxdWVzdCkpIHtcbiAgICAgIC8vIGVzdGFibGlzaCB0aW1lb3V0IGZvciByZW5kZXJpbmdcbiAgICAgIGNvbnN0IHRpbWVvdXQgPSB0aGlzLmdldFRpbWVvdXQocmVxdWVzdCk7XG4gICAgICByZXF1ZXN0VGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICByZXF1ZXN0VGltZW91dCA9IHVuZGVmaW5lZDtcbiAgICAgICAgdGhpcy5mYWxsYmFja1RvQ3NyKHJlc3BvbnNlLCBmaWxlUGF0aCwgY2FsbGJhY2spO1xuICAgICAgICB0aGlzLmxvZyhcbiAgICAgICAgICBgU1NSIHJlbmRlcmluZyBleGNlZWRlZCB0aW1lb3V0ICR7dGltZW91dH0sIGZhbGxiYWNraW5nIHRvIENTUiBmb3IgJHtyZXF1ZXN0Py5vcmlnaW5hbFVybH1gLFxuICAgICAgICAgIGZhbHNlXG4gICAgICAgICk7XG4gICAgICB9LCB0aW1lb3V0KTtcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gSGVyZSB3ZSByZXNwb25kIHdpdGggdGhlIGZhbGxiYWNrIHRvIENTUiwgYnV0IHdlIGRvbid0IGByZXR1cm5gLlxuICAgICAgLy8gV2UgbGV0IHRoZSBhY3R1YWwgcmVuZGVyaW5nIHRhc2sgdG8gaGFwcGVuIGluIHRoZSBiYWNrZ3JvdW5kXG4gICAgICAvLyB0byBldmVudHVhbGx5IHN0b3JlIHRoZSByZW5kZXJlZCByZXN1bHQgaW4gdGhlIGNhY2hlLlxuICAgICAgdGhpcy5mYWxsYmFja1RvQ3NyKHJlc3BvbnNlLCBmaWxlUGF0aCwgY2FsbGJhY2spO1xuICAgIH1cblxuICAgIGNvbnN0IHJlbmRlcmluZ0tleSA9IHRoaXMuZ2V0UmVuZGVyaW5nS2V5KHJlcXVlc3QpO1xuICAgIGNvbnN0IHJlbmRlckNhbGxiYWNrOiBTc3JDYWxsYmFja0ZuID0gKGVyciwgaHRtbCk6IHZvaWQgPT4ge1xuICAgICAgaWYgKHJlcXVlc3RUaW1lb3V0KSB7XG4gICAgICAgIC8vIGlmIHJlcXVlc3QgaXMgc3RpbGwgd2FpdGluZyBmb3IgcmVuZGVyLCByZXR1cm4gaXRcbiAgICAgICAgY2xlYXJUaW1lb3V0KHJlcXVlc3RUaW1lb3V0KTtcbiAgICAgICAgY2FsbGJhY2soZXJyLCBodG1sKTtcblxuICAgICAgICB0aGlzLmxvZyhcbiAgICAgICAgICBgUmVxdWVzdCBpcyByZXNvbHZlZCB3aXRoIHRoZSBTU1IgcmVuZGVyaW5nIHJlc3VsdCAoJHtyZXF1ZXN0Py5vcmlnaW5hbFVybH0pYFxuICAgICAgICApO1xuXG4gICAgICAgIC8vIHN0b3JlIHRoZSByZW5kZXIgb25seSBpZiBjYWNoaW5nIGlzIGVuYWJsZWRcbiAgICAgICAgaWYgKHRoaXMuc3NyT3B0aW9ucz8uY2FjaGUpIHtcbiAgICAgICAgICB0aGlzLnJlbmRlcmluZ0NhY2hlLnN0b3JlKHJlbmRlcmluZ0tleSwgZXJyLCBodG1sKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aGlzLnJlbmRlcmluZ0NhY2hlLmNsZWFyKHJlbmRlcmluZ0tleSk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIHN0b3JlIHRoZSByZW5kZXIgZm9yIGZ1dHVyZSB1c2VcbiAgICAgICAgdGhpcy5yZW5kZXJpbmdDYWNoZS5zdG9yZShyZW5kZXJpbmdLZXksIGVyciwgaHRtbCk7XG4gICAgICB9XG4gICAgfTtcblxuICAgIHRoaXMuaGFuZGxlUmVuZGVyKHtcbiAgICAgIGZpbGVQYXRoLFxuICAgICAgb3B0aW9ucyxcbiAgICAgIHJlbmRlckNhbGxiYWNrLFxuICAgICAgcmVxdWVzdCxcbiAgICB9KTtcbiAgfVxuXG4gIHByb3RlY3RlZCBsb2cobWVzc2FnZTogc3RyaW5nLCBkZWJ1ZyA9IHRydWUpOiB2b2lkIHtcbiAgICBpZiAoIWRlYnVnIHx8IHRoaXMuc3NyT3B0aW9ucz8uZGVidWcpIHtcbiAgICAgIGNvbnNvbGUubG9nKG1lc3NhZ2UpO1xuICAgIH1cbiAgfVxuXG4gIC8qKiBSZXRyaWV2ZSB0aGUgZG9jdW1lbnQgZnJvbSB0aGUgY2FjaGUgb3IgdGhlIGZpbGVzeXN0ZW0gKi9cbiAgcHJvdGVjdGVkIGdldERvY3VtZW50KGZpbGVQYXRoOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIGxldCBkb2MgPSB0aGlzLnRlbXBsYXRlQ2FjaGUuZ2V0KGZpbGVQYXRoKTtcblxuICAgIGlmICghZG9jKSB7XG4gICAgICAvLyBmcy5yZWFkRmlsZVN5bmMgY291bGQgYmUgbWlzc2luZyBpbiBhIGJyb3dzZXIsIHNwZWNpZmljYWxseVxuICAgICAgLy8gaW4gYSB1bml0IHRlc3RzIHdpdGggeyBub2RlOiB7IGZzOiAnZW1wdHknIH0gfSB3ZWJwYWNrIGNvbmZpZ3VyYXRpb25cbiAgICAgIGRvYyA9IGZzPy5yZWFkRmlsZVN5bmMgPyBmcy5yZWFkRmlsZVN5bmMoZmlsZVBhdGgsICd1dGYtOCcpIDogJyc7XG4gICAgICB0aGlzLnRlbXBsYXRlQ2FjaGUuc2V0KGZpbGVQYXRoLCBkb2MpO1xuICAgIH1cblxuICAgIHJldHVybiBkb2M7XG4gIH1cblxuICAvKipcbiAgICogRGVsZWdhdGVzIHRoZSByZW5kZXIgdG8gdGhlIG9yaWdpbmFsIF9Bbmd1bGFyIFVuaXZlcnNhbCBleHByZXNzIGVuZ2luZV8uXG4gICAqXG4gICAqIEluIGNhc2Ugd2hlbiB0aGUgY29uZmlnIGByZXVzZUN1cnJlbnRSZW5kZXJpbmdgIGlzIGVuYWJsZWQgYW5kICoqaWYgdGhlcmUgaXMgYWxyZWFkeSBhIHBlbmRpbmdcbiAgICogcmVuZGVyIHRhc2sgZm9yIHRoZSBzYW1lIHJlbmRlcmluZyBrZXkqKiwgaXQgZG9lc24ndCBkZWxlZ2F0ZSBhIG5ldyByZW5kZXIgdG8gQW5ndWxhciBVbml2ZXJzYWwuXG4gICAqIEluc3RlYWQsIGl0IHdhaXRzIGZvciB0aGUgY3VycmVudCByZW5kZXJpbmcgdG8gY29tcGxldGUgYW5kIHRoZW4gcmV1c2UgdGhlIHJlc3VsdCBmb3IgYWxsIHdhaXRpbmcgcmVxdWVzdHMuXG4gICAqL1xuICBwcml2YXRlIGhhbmRsZVJlbmRlcih7XG4gICAgZmlsZVBhdGgsXG4gICAgb3B0aW9ucyxcbiAgICByZW5kZXJDYWxsYmFjayxcbiAgICByZXF1ZXN0LFxuICB9OiB7XG4gICAgZmlsZVBhdGg6IHN0cmluZztcbiAgICBvcHRpb25zOiBhbnk7XG4gICAgcmVuZGVyQ2FsbGJhY2s6IFNzckNhbGxiYWNrRm47XG4gICAgcmVxdWVzdDogUmVxdWVzdDtcbiAgfSk6IHZvaWQge1xuICAgIGlmICghdGhpcy5zc3JPcHRpb25zPy5yZXVzZUN1cnJlbnRSZW5kZXJpbmcpIHtcbiAgICAgIHRoaXMuc3RhcnRSZW5kZXIoe1xuICAgICAgICBmaWxlUGF0aCxcbiAgICAgICAgb3B0aW9ucyxcbiAgICAgICAgcmVuZGVyQ2FsbGJhY2ssXG4gICAgICAgIHJlcXVlc3QsXG4gICAgICB9KTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBjb25zdCByZW5kZXJpbmdLZXkgPSB0aGlzLmdldFJlbmRlcmluZ0tleShyZXF1ZXN0KTtcbiAgICBpZiAoIXRoaXMucmVuZGVyQ2FsbGJhY2tzLmhhcyhyZW5kZXJpbmdLZXkpKSB7XG4gICAgICB0aGlzLnJlbmRlckNhbGxiYWNrcy5zZXQocmVuZGVyaW5nS2V5LCBbXSk7XG4gICAgfVxuICAgIHRoaXMucmVuZGVyQ2FsbGJhY2tzLmdldChyZW5kZXJpbmdLZXkpPy5wdXNoKHJlbmRlckNhbGxiYWNrKTtcblxuICAgIGlmICghdGhpcy5yZW5kZXJpbmdDYWNoZS5pc1JlbmRlcmluZyhyZW5kZXJpbmdLZXkpKSB7XG4gICAgICB0aGlzLnN0YXJ0UmVuZGVyKHtcbiAgICAgICAgZmlsZVBhdGgsXG4gICAgICAgIG9wdGlvbnMsXG4gICAgICAgIHJlcXVlc3QsXG4gICAgICAgIHJlbmRlckNhbGxiYWNrOiAoZXJyLCBodG1sKSA9PiB7XG4gICAgICAgICAgLy8gU2hhcmUgdGhlIHJlc3VsdCBvZiB0aGUgcmVuZGVyIHdpdGggYWxsIGF3YWl0aW5nIHJlcXVlc3RzIGZvciB0aGUgc2FtZSBrZXk6XG5cbiAgICAgICAgICAvLyBOb3RlOiB3ZSBhY2Nlc3MgdGhlIE1hcCBhdCB0aGUgbW9tZW50IG9mIHRoZSByZW5kZXIgZmluaXNoZWQgKGRvbid0IHN0b3JlIHZhbHVlIGluIGEgbG9jYWwgdmFyaWFibGUpLFxuICAgICAgICAgIC8vICAgICAgIGJlY2F1c2UgaW4gdGhlIG1lYW50aW1lIHNvbWV0aGluZyBtaWdodCBoYXZlIGRlbGV0ZWQgdGhlIHZhbHVlIChpLmUuIHdoZW4gYG1heFJlbmRlclRpbWVgIHBhc3NlZCkuXG4gICAgICAgICAgdGhpcy5yZW5kZXJDYWxsYmFja3NcbiAgICAgICAgICAgIC5nZXQocmVuZGVyaW5nS2V5KVxuICAgICAgICAgICAgPy5mb3JFYWNoKChjYikgPT4gY2IoZXJyLCBodG1sKSk7IC8vIHBhc3MgdGhlIHNoYXJlZCByZXN1bHQgdG8gYWxsIHdhaXRpbmcgcmVuZGVyaW5nIGNhbGxiYWNrc1xuICAgICAgICAgIHRoaXMucmVuZGVyQ2FsbGJhY2tzLmRlbGV0ZShyZW5kZXJpbmdLZXkpO1xuICAgICAgICB9LFxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgdGhpcy5sb2coXG4gICAgICBgUmVxdWVzdCBpcyB3YWl0aW5nIGZvciB0aGUgU1NSIHJlbmRlcmluZyB0byBjb21wbGV0ZSAoJHtyZXF1ZXN0Py5vcmlnaW5hbFVybH0pYFxuICAgICk7XG4gIH1cblxuICAvKipcbiAgICogRGVsZWdhdGVzIHRoZSByZW5kZXIgdG8gdGhlIG9yaWdpbmFsIF9Bbmd1bGFyIFVuaXZlcnNhbCBleHByZXNzIGVuZ2luZV8uXG4gICAqXG4gICAqIFRoZXJlIGlzIG5vIHdheSB0byBhYm9ydCB0aGUgcnVubmluZyByZW5kZXIgb2YgQW5ndWxhciBVbml2ZXJzYWwuXG4gICAqIFNvIGlmIHRoZSByZW5kZXIgZG9lc24ndCBjb21wbGV0ZSBpbiB0aGUgY29uZmlndXJlZCBgbWF4UmVuZGVyVGltZWAsXG4gICAqIHdlIGp1c3QgY29uc2lkZXIgdGhlIHJlbmRlciB0YXNrIGFzIGhhbmdpbmcgKG5vdGU6IGl0J3MgYSBwb3RlbnRpYWwgbWVtb3J5IGxlYWshKS5cbiAgICogTGF0ZXIgb24sIGV2ZW4gaWYgdGhlIHJlbmRlciBjb21wbGV0ZXMgc29tZXdoZW4gaW4gdGhlIGZ1dHVyZSwgd2Ugd2lsbCBpZ25vcmVcbiAgICogaXRzIHJlc3VsdC5cbiAgICovXG4gIHByaXZhdGUgc3RhcnRSZW5kZXIoe1xuICAgIGZpbGVQYXRoLFxuICAgIG9wdGlvbnMsXG4gICAgcmVuZGVyQ2FsbGJhY2ssXG4gICAgcmVxdWVzdCxcbiAgfToge1xuICAgIGZpbGVQYXRoOiBzdHJpbmc7XG4gICAgb3B0aW9uczogYW55O1xuICAgIHJlbmRlckNhbGxiYWNrOiBTc3JDYWxsYmFja0ZuO1xuICAgIHJlcXVlc3Q6IFJlcXVlc3Q7XG4gIH0pOiB2b2lkIHtcbiAgICBjb25zdCByZW5kZXJpbmdLZXkgPSB0aGlzLmdldFJlbmRlcmluZ0tleShyZXF1ZXN0KTtcblxuICAgIC8vIFNldHRpbmcgdGhlIHRpbWVvdXQgZm9yIGhhbmdpbmcgcmVuZGVycyB0aGF0IG1pZ2h0IG5vdCBldmVyIGZpbmlzaCBkdWUgdG8gdmFyaW91cyByZWFzb25zLlxuICAgIC8vIEFmdGVyIHRoZSBjb25maWd1cmVkIGBtYXhSZW5kZXJUaW1lYCBwYXNzZXMsIHdlIGNvbnNpZGVyIHRoZSByZW5kZXJpbmcgdGFzayBhcyBoYW5naW5nLFxuICAgIC8vIGFuZCByZWxlYXNlIHRoZSBjb25jdXJyZW5jeSBzbG90IGFuZCBmb3JnZXQgYWxsIGNhbGxiYWNrcyB3YWl0aW5nIGZvciB0aGUgcmVuZGVyJ3MgcmVzdWx0LlxuICAgIGxldCBtYXhSZW5kZXJUaW1lb3V0OiBOb2RlSlMuVGltZW91dCB8IHVuZGVmaW5lZCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgdGhpcy5yZW5kZXJpbmdDYWNoZS5jbGVhcihyZW5kZXJpbmdLZXkpO1xuICAgICAgbWF4UmVuZGVyVGltZW91dCA9IHVuZGVmaW5lZDtcbiAgICAgIHRoaXMuY3VycmVudENvbmN1cnJlbmN5LS07XG4gICAgICBpZiAodGhpcy5zc3JPcHRpb25zPy5yZXVzZUN1cnJlbnRSZW5kZXJpbmcpIHtcbiAgICAgICAgdGhpcy5yZW5kZXJDYWxsYmFja3MuZGVsZXRlKHJlbmRlcmluZ0tleSk7XG4gICAgICB9XG4gICAgICB0aGlzLmxvZyhcbiAgICAgICAgYFJlbmRlcmluZyBvZiAke3JlcXVlc3Q/Lm9yaWdpbmFsVXJsfSB3YXMgbm90IGFibGUgdG8gY29tcGxldGUuIFRoaXMgbWlnaHQgY2F1c2UgbWVtb3J5IGxlYWtzIWAsXG4gICAgICAgIGZhbHNlXG4gICAgICApO1xuICAgIH0sIHRoaXMuc3NyT3B0aW9ucz8ubWF4UmVuZGVyVGltZSA/PyAzMDAwMDApOyAvLyAzMDAwMDBtcyA9PSA1IG1pbnV0ZXNcblxuICAgIHRoaXMubG9nKGBSZW5kZXJpbmcgc3RhcnRlZCAoJHtyZXF1ZXN0Py5vcmlnaW5hbFVybH0pYCk7XG4gICAgdGhpcy5yZW5kZXJpbmdDYWNoZS5zZXRBc1JlbmRlcmluZyhyZW5kZXJpbmdLZXkpO1xuICAgIHRoaXMuY3VycmVudENvbmN1cnJlbmN5Kys7XG5cbiAgICB0aGlzLmV4cHJlc3NFbmdpbmUoZmlsZVBhdGgsIG9wdGlvbnMsIChlcnIsIGh0bWwpID0+IHtcbiAgICAgIGlmICghbWF4UmVuZGVyVGltZW91dCkge1xuICAgICAgICAvLyBpZ25vcmUgdGhpcyByZW5kZXIncyByZXN1bHQgYmVjYXVzZSBpdCBleGNlZWRlZCBtYXhSZW5kZXJUaW1lb3V0XG4gICAgICAgIHRoaXMubG9nKFxuICAgICAgICAgIGBSZW5kZXJpbmcgb2YgJHtyZXF1ZXN0Lm9yaWdpbmFsVXJsfSBjb21wbGV0ZWQgYWZ0ZXIgdGhlIHNwZWNpZmllZCBtYXhSZW5kZXJUaW1lLCB0aGVyZWZvcmUgaXQgd2FzIGlnbm9yZWQuYCxcbiAgICAgICAgICBmYWxzZVxuICAgICAgICApO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBjbGVhclRpbWVvdXQobWF4UmVuZGVyVGltZW91dCk7XG5cbiAgICAgIHRoaXMubG9nKGBSZW5kZXJpbmcgY29tcGxldGVkICgke3JlcXVlc3Q/Lm9yaWdpbmFsVXJsfSlgKTtcbiAgICAgIHRoaXMuY3VycmVudENvbmN1cnJlbmN5LS07XG5cbiAgICAgIHJlbmRlckNhbGxiYWNrKGVyciwgaHRtbCk7XG4gICAgfSk7XG4gIH1cbn1cbiJdfQ==