UNPKG

angular-aap-auth

Version:

## This library is deprecated. Please, use [ng-ebi-authorization](https://www.npmjs.com/package/ng-ebi-authorization).

506 lines (498 loc) 47.8 kB
import { Injectable, InjectionToken, Inject, RendererFactory2, NgModule, Optional, SkipSelf } from '@angular/core'; import { JwtHelperService } from '@auth0/angular-jwt'; import { BehaviorSubject } from 'rxjs'; import { map } from 'rxjs/operators'; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ /** * The purpose of this very simple service is to interface between the * AuthService and the specific token manipulation routing of JwtHelperService. * In this way, if in the future we want to replace JwtHelperService by * another service, AuthService doesn't need to be modified, only this service. */ class TokenService { /** * @param {?} _jwt */ constructor(_jwt) { this._jwt = _jwt; } /** * @return {?} */ getToken() { return this._jwt.tokenGetter(); } /** * @return {?} */ getTokenExpirationDate() { try { return this._jwt.getTokenExpirationDate(); } catch (/** @type {?} */ e) { return null; } } /** * @return {?} */ isTokenValid() { try { return !this._jwt.isTokenExpired(); } catch (/** @type {?} */ error) { return false; } } /** * Get claims from the token. * * @template T, C * @param {?} claim The name of the claim * @param {?} defaultValue The default value returned in case of error * * @return {?} claim or default value */ getClaim(claim, defaultValue) { try { const /** @type {?} */ value = /** @type {?} */ (this._jwt.decodeToken()[claim]); if (value === undefined) { return defaultValue; } return value; } catch (/** @type {?} */ e) { return defaultValue; } } } TokenService.decorators = [ { type: Injectable }, ]; /** @nocollapse */ TokenService.ctorParameters = () => [ { type: JwtHelperService } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ const /** @type {?} */ AAP_CONFIG = new InjectionToken('AAP_CONFIG'); /** * @return {?} */ function getToken() { return localStorage.getItem('id_token') || ''; } /** * @return {?} */ function removeToken() { return localStorage.removeItem('id_token'); } /** * @param {?} newToken * @return {?} */ function updateToken(newToken) { return localStorage.setItem('id_token', newToken); } const /** @type {?} */ DEFAULT_CONF = { aapURL: 'https://api.aai.ebi.ac.uk', tokenGetter: getToken, tokenRemover: removeToken, tokenUpdater: updateToken }; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ class AuthService { /** * @param {?} _rendererFactory * @param {?} _tokenService * @param {?} config */ constructor(_rendererFactory, _tokenService, config) { this._rendererFactory = _rendererFactory; this._tokenService = _tokenService; this.config = config; this._credentials = new BehaviorSubject(null); this._loginCallbacks = []; this._logoutCallbacks = []; this._timeoutID = null; this._commKeyName = 'AngularAapAuthUpdated'; this._commKeyUpdater = () => localStorage.setItem(this._commKeyName, '' + new Date().getTime()); this._domain = encodeURIComponent(window.location.origin); this._appURL = config.aapURL.replace(/\/$/, ''); this._storageUpdater = config.tokenUpdater; if (config.tokenRemover) { this._storageRemover = config.tokenRemover; } else { this._storageRemover = () => config.tokenUpdater(null); } const /** @type {?} */ renderer = this._rendererFactory.createRenderer(null, null); this._listenLoginMessage(renderer); this._listenChangesFromOtherWindows(renderer); this._updateCredentials(); // TODO: experiment with setTimeOut } /** * @return {?} */ isAuthenticated() { return this._credentials.asObservable().pipe(map(credentials => credentials ? true : false)); } /** * @return {?} */ credentials() { return this._credentials.asObservable(); } /** * @return {?} */ realname() { return this._credentials.asObservable().pipe(map(credentials => credentials ? credentials.realname : null)); } /** * @return {?} */ username() { return this._credentials.asObservable().pipe(map(credentials => credentials ? credentials.username : null)); } /** * @return {?} */ token() { return this._credentials.asObservable().pipe(map(credentials => credentials ? credentials.token : null)); } /** * Functions that opens a window instead of a tab. * * See method _filterLoginOptions regarding security risks of certain * LoginOptions. * * @param {?=} loginOptions Options passed as URL parameters to the SSO. * @param {?=} width Pixel width of the login window. * @param {?=} height Pixel height of the login window. * @param {?=} top Position of the top corners. If it is a negative * number it centres the login window on the screen. * @param {?=} left Position of the left corners. If it is a negative * number it centres the login window on the screen. * @return {?} */ windowOpen(loginOptions, width = 650, height = 1000, top = -1, left = -1) { if (left < 0) { const /** @type {?} */ screenWidth = screen.width; if (screenWidth > width) { left = Math.round(screenWidth / 2 - width / 2); } } if (top < 0) { const /** @type {?} */ screenHeight = screen.height; if (screenHeight > height) { top = Math.round(screenHeight / 2 - height / 2); } } const /** @type {?} */ windowOptions = [ `width=${width}`, `height=${height}`, `left=${left}`, `top=${top}`, 'personalbar=no', 'toolbar=no', 'scrollbars=yes', 'resizable=yes', 'directories=no', 'location=no', 'menubar=no', 'titlebar=no', 'toolbar=no' ]; const /** @type {?} */ loginWindow = window.open(this.getSSOURL(loginOptions), 'Sign in to Elixir', windowOptions.join(',')); if (loginWindow) { loginWindow.focus(); } } /** * Functions that opens a tab (in modern browser). * * See method _filterLoginOptions regarding security risks of certain * LoginOptions. * * @param {?=} loginOptions Options passed as URL parameters to the SSO. * @return {?} */ tabOpen(loginOptions) { const /** @type {?} */ loginWindow = window.open(this.getSSOURL(loginOptions), 'Sign in to Elixir'); if (loginWindow) { loginWindow.focus(); } } /** * Produces a URL that allows logging into the single sign on (SSO) page. * The URL cans be opened in a new tab using target="_blank", * or in a new window using window.open(). * * See method _filterLoginOptions regarding security risks of certain * LoginOptions. * * @param {?=} options * @return {?} The SSO URL. * */ getSSOURL(options) { let /** @type {?} */ extra = ''; if (options) { this._filterLoginOptions(options); extra = Object.keys(options) .map(key => [key, options[key]]) .reduce((accumulator, keyvalue) => `${accumulator}&${keyvalue[0]}=${keyvalue[1]}`, ''); } return `${this._appURL}/sso?from=${this._domain}${extra}`; } /** * Filters options that are unsecure. * * See the advance options that can be requested through the options parameter: * https://api.aai.ebi.ac.uk/docs/authentication/authentication.index.html#_common_attributes * * The time to live paramenter (ttl) default value is 60 minutes. It is a * big security risk to request longer ttl. If a third party gets hold of * such token, means that they could use it for a day, week, year * (essentially, like having the username/password). * * @param {?} options * @return {?} */ _filterLoginOptions(options) { if (Object.keys(options).indexOf('ttl') > -1) { const /** @type {?} */ ttl = +options['ttl']; const /** @type {?} */ softLimit = 60; const /** @type {?} */ hardLimit = 60 * 24; if (ttl > hardLimit) { window.console.error(`Login requested with an expiration longer than ${hardLimit} minutes! This is not allowed.`); window.console.error(`Expiration request reset to ${hardLimit} minutes.`); options['ttl'] = '' + hardLimit; } else if (ttl > softLimit) { window.console.warn(`Login requested with an expiration longer than ${softLimit} minutes!`); } } } /** * Functions that logs out the user. * It triggers the logout callbacks. * It is an arrow function (lambda) because in that way it has a reference * to 'this' when used in setTimeout call. * @return {?} */ logOut() { this._storageRemover(); this._updateCredentials(); // Triggers updating other windows this._commKeyUpdater(); } /** * Add a callback to the LogIn event. * * @param {?} callback The Function called when the login event is triggered and the * JWT token is received and accepted. * * @return {?} The event registration id (necessary to unregister the event). */ addLogInEventListener(callback) { return this._loginCallbacks.push(callback); } /** * Remove a callback from the LogIn event. * * @param {?} id The id given when event listener was added. * * @return {?} true when remove successfully, false otherwise. */ removeLogInEventListener(id) { return delete this._loginCallbacks[id - 1]; } /** * Add a callback to the LogOut event. * * @param {?} callback The Function called when the logout event is triggered and the * JWT token is received and accepted. * * @return {?} The registration id (necessary to unregister the event). */ addLogOutEventListener(callback) { return this._logoutCallbacks.push(callback); } /** * Remove a callback from the LogOut event. * * @param {?} id The id given when event listener was added. * * @return {?} true when remove successfully, false otherwise. */ removeLogOutEventListener(id) { return delete this._logoutCallbacks[id - 1]; } /** * Listen for login messages from other windows. * These messages contain the tokens from the AAP. * If a token is received then the callbacks are triggered. * @param {?} renderer * @return {?} */ _listenLoginMessage(renderer) { renderer.listen('window', 'message', (event) => { if (!this.messageIsAcceptable(event)) { return; } this._storageUpdater(event.data); event.source.close(); this._updateCredentials(); // Triggers updating other windows this._commKeyUpdater(); }); } /** * Listen to changes in the token from *other* windows. * * For inter-window communication messages are transmitted trough changes * on a dummy storage key property: '_commKeyName'. * * Notice that changes in the '_commKeyName' produced by this class doesn't * trigger this event. * @param {?} renderer * @return {?} */ _listenChangesFromOtherWindows(renderer) { renderer.listen('window', 'storage', (event) => { if (event.key === this._commKeyName) { this._updateCredentials(); } }); } /** * Check if the message is coming from the same domain we use to generate * the SSO URL, otherwise it's iffy and shouldn't trust it. * @param {?} event * @return {?} */ messageIsAcceptable(event) { return event.origin === this._appURL; } /** * @return {?} */ _updateCredentials() { const /** @type {?} */ isAuthenticated = this._loggedIn(); if (this._timeoutID) { window.clearTimeout(this._timeoutID); } if (isAuthenticated) { this._credentials.next({ realname: /** @type {?} */ (this._getRealName()), username: /** @type {?} */ (this._getUserName()), token: /** @type {?} */ (this._getToken()) }); this._loginCallbacks.map(callback => callback && callback()); // Schedule future logout event base on token expiration const /** @type {?} */ expireDate = /** @type {?} */ (this._tokenService.getTokenExpirationDate()); // Coercing dates to numbers with the unary operator '+' const /** @type {?} */ delay = +expireDate - +new Date(); this._timeoutID = window.setTimeout(() => this.logOut(), delay); } else { this._storageRemover(); // Cleanup possible left behind token this._credentials.next(null); this._logoutCallbacks.map(callback => callback && callback()); } } /** * Check if there's a user logging on and whether the token is still valid. * * @return {?} Whether the user user is authenticated or not. */ _loggedIn() { return this._tokenService.isTokenValid(); } /** * @return {?} */ _getToken() { return this._tokenService.getToken(); } /** * @return {?} */ _getUserName() { return this._tokenService.getClaim('email', null); } /** * @return {?} */ _getRealName() { return this._tokenService.getClaim('name', null); } } AuthService.decorators = [ { type: Injectable }, ]; /** @nocollapse */ AuthService.ctorParameters = () => [ { type: RendererFactory2 }, { type: TokenService }, { type: undefined, decorators: [{ type: Inject, args: [AAP_CONFIG,] }] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ class AuthModule { /** * @param {?} parentModule */ constructor(parentModule) { if (parentModule) { throw new Error('AuthModule is already loaded. It should only be imported in your application\'s main module.'); } } /** * @param {?=} options * @return {?} */ static forRoot(options) { return { ngModule: AuthModule, providers: [ TokenService, { provide: AAP_CONFIG, useValue: options ? options : DEFAULT_CONF }, AuthService ] }; } } AuthModule.decorators = [ { type: NgModule, args: [{},] }, ]; /** @nocollapse */ AuthModule.ctorParameters = () => [ { type: AuthModule, decorators: [{ type: Optional }, { type: SkipSelf }] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ export { AuthModule, AuthService, TokenService, AAP_CONFIG as ɵb, DEFAULT_CONF as ɵf, getToken as ɵc, removeToken as ɵd, updateToken as ɵe }; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYW5ndWxhci1hYXAtYXV0aC5qcy5tYXAiLCJzb3VyY2VzIjpbIm5nOi8vYW5ndWxhci1hYXAtYXV0aC9hcHAvbW9kdWxlcy9hdXRoL3Rva2VuLnNlcnZpY2UudHMiLCJuZzovL2FuZ3VsYXItYWFwLWF1dGgvYXBwL21vZHVsZXMvYXV0aC9hdXRoLmNvbmZpZy50cyIsIm5nOi8vYW5ndWxhci1hYXAtYXV0aC9hcHAvbW9kdWxlcy9hdXRoL2F1dGguc2VydmljZS50cyIsIm5nOi8vYW5ndWxhci1hYXAtYXV0aC9hcHAvbW9kdWxlcy9hdXRoL2F1dGgubW9kdWxlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gICAgSW5qZWN0YWJsZVxufSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuaW1wb3J0IHtcbiAgICBKd3RIZWxwZXJTZXJ2aWNlXG59IGZyb20gJ0BhdXRoMC9hbmd1bGFyLWp3dCc7XG5cbi8qKlxuICogVGhlIHB1cnBvc2Ugb2YgdGhpcyB2ZXJ5IHNpbXBsZSBzZXJ2aWNlIGlzIHRvIGludGVyZmFjZSBiZXR3ZWVuIHRoZVxuICogQXV0aFNlcnZpY2UgYW5kIHRoZSBzcGVjaWZpYyB0b2tlbiBtYW5pcHVsYXRpb24gcm91dGluZyBvZiBKd3RIZWxwZXJTZXJ2aWNlLlxuICogSW4gdGhpcyB3YXksIGlmIGluIHRoZSBmdXR1cmUgd2Ugd2FudCB0byByZXBsYWNlIEp3dEhlbHBlclNlcnZpY2UgYnlcbiAqIGFub3RoZXIgc2VydmljZSwgQXV0aFNlcnZpY2UgZG9lc24ndCBuZWVkIHRvIGJlIG1vZGlmaWVkLCBvbmx5IHRoaXMgc2VydmljZS5cbiAqL1xuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIFRva2VuU2VydmljZSB7XG5cbiAgICBjb25zdHJ1Y3RvcihcbiAgICAgICAgcHJpdmF0ZSBfand0OiBKd3RIZWxwZXJTZXJ2aWNlXG4gICAgKSB7fVxuXG4gICAgcHVibGljIGdldFRva2VuKCk6IHN0cmluZyB8IG51bGwge1xuICAgICAgICByZXR1cm4gdGhpcy5fand0LnRva2VuR2V0dGVyKCk7XG4gICAgfVxuXG4gICAgcHVibGljIGdldFRva2VuRXhwaXJhdGlvbkRhdGUoKTogRGF0ZSB8IG51bGwge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuX2p3dC5nZXRUb2tlbkV4cGlyYXRpb25EYXRlKCk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHVibGljIGlzVG9rZW5WYWxpZCgpOiBib29sZWFuIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHJldHVybiAhdGhpcy5fand0LmlzVG9rZW5FeHBpcmVkKCk7XG4gICAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXQgY2xhaW1zIGZyb20gdGhlIHRva2VuLlxuICAgICAqXG4gICAgICogQHBhcmFtIGNsYWltIFRoZSBuYW1lIG9mIHRoZSBjbGFpbVxuICAgICAqIEBwYXJhbSBkZWZhdWx0VmFsdWUgVGhlIGRlZmF1bHQgdmFsdWUgcmV0dXJuZWQgaW4gY2FzZSBvZiBlcnJvclxuICAgICAqXG4gICAgICogQHJldHVybnMgY2xhaW0gb3IgZGVmYXVsdCB2YWx1ZVxuICAgICAqL1xuICAgIHB1YmxpYyBnZXRDbGFpbSA8IFQsIEMgPiAoY2xhaW06IHN0cmluZywgZGVmYXVsdFZhbHVlOiBDKTogVCB8IEMge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgY29uc3QgdmFsdWUgPSA8IFQgPiB0aGlzLl9qd3QuZGVjb2RlVG9rZW4oKVtjbGFpbV07XG4gICAgICAgICAgICBpZiAodmFsdWUgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBkZWZhdWx0VmFsdWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gdmFsdWU7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHJldHVybiBkZWZhdWx0VmFsdWU7XG4gICAgICAgIH1cbiAgICB9XG59XG4iLCJpbXBvcnQge1xuICAgIEluamVjdGlvblRva2VuXG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuXG5leHBvcnQgaW50ZXJmYWNlIEF1dGhDb25maWcge1xuICAgIGFhcFVSTDogc3RyaW5nO1xuICAgIHRva2VuR2V0dGVyOiAoKSA9PiBzdHJpbmc7XG4gICAgdG9rZW5SZW1vdmVyPzogKCkgPT4gdm9pZDtcbiAgICB0b2tlblVwZGF0ZXI6IChuZXdUb2tlbjogYW55KSA9PiB2b2lkO1xufVxuXG5leHBvcnQgY29uc3QgQUFQX0NPTkZJRyA9IG5ldyBJbmplY3Rpb25Ub2tlbiA8IEF1dGhDb25maWcgPiAoJ0FBUF9DT05GSUcnKTtcblxuZXhwb3J0IGZ1bmN0aW9uIGdldFRva2VuKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGxvY2FsU3RvcmFnZS5nZXRJdGVtKCdpZF90b2tlbicpIHx8ICcnO1xufVxuZXhwb3J0IGZ1bmN0aW9uIHJlbW92ZVRva2VuKCk6IHZvaWQge1xuICAgIHJldHVybiBsb2NhbFN0b3JhZ2UucmVtb3ZlSXRlbSgnaWRfdG9rZW4nKTtcbn1cbmV4cG9ydCBmdW5jdGlvbiB1cGRhdGVUb2tlbihuZXdUb2tlbjogc3RyaW5nKTogdm9pZCB7XG4gICAgcmV0dXJuIGxvY2FsU3RvcmFnZS5zZXRJdGVtKCdpZF90b2tlbicsIG5ld1Rva2VuKTtcbn1cbmV4cG9ydCBjb25zdCBERUZBVUxUX0NPTkY6IEF1dGhDb25maWcgPSB7XG4gICAgYWFwVVJMOiAnaHR0cHM6Ly9hcGkuYWFpLmViaS5hYy51aycsXG4gICAgdG9rZW5HZXR0ZXI6IGdldFRva2VuLFxuICAgIHRva2VuUmVtb3ZlcjogcmVtb3ZlVG9rZW4sXG4gICAgdG9rZW5VcGRhdGVyOiB1cGRhdGVUb2tlblxufTtcbiIsImltcG9ydCB7XG4gICAgSW5qZWN0YWJsZSxcbiAgICBJbmplY3QsXG4gICAgUmVuZGVyZXJGYWN0b3J5MixcbiAgICBSZW5kZXJlcjJcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQge1xuICAgIE9ic2VydmFibGVcbn0gZnJvbSAncnhqcyc7XG5pbXBvcnQge1xuICAgIEJlaGF2aW9yU3ViamVjdCxcbn0gZnJvbSAncnhqcyc7XG5pbXBvcnQge1xuICAgIG1hcFxufSBmcm9tICdyeGpzL29wZXJhdG9ycyc7XG5cbmltcG9ydCB7XG4gICAgQUFQX0NPTkZJRyxcbiAgICBBdXRoQ29uZmlnXG59IGZyb20gJy4vYXV0aC5jb25maWcnO1xuaW1wb3J0IHtcbiAgICBUb2tlblNlcnZpY2Vcbn0gZnJvbSAnLi90b2tlbi5zZXJ2aWNlJztcblxuZXhwb3J0IGludGVyZmFjZSBMb2dpbk9wdGlvbnMge1xuICAgIFtrZXk6IHN0cmluZ106IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBDcmVkZW50aWFscyB7XG4gICAgcmVhbG5hbWU6IHN0cmluZztcbiAgICB1c2VybmFtZTogc3RyaW5nO1xuICAgIHRva2VuOiBzdHJpbmc7XG59XG5cbkBJbmplY3RhYmxlKClcbmV4cG9ydCBjbGFzcyBBdXRoU2VydmljZSB7XG5cbiAgICBwcml2YXRlIF9jcmVkZW50aWFscyA9IG5ldyBCZWhhdmlvclN1YmplY3QgPCBDcmVkZW50aWFscyB8IG51bGwgPiAobnVsbCk7XG5cbiAgICBwcml2YXRlIF9sb2dpbkNhbGxiYWNrczogRnVuY3Rpb25bXSA9IFtdO1xuICAgIHByaXZhdGUgX2xvZ291dENhbGxiYWNrczogRnVuY3Rpb25bXSA9IFtdO1xuXG4gICAgcHJpdmF0ZSBfdGltZW91dElEOiBudW1iZXIgfCBudWxsID0gbnVsbDtcblxuICAgIC8vIENvbmZpZ3VyYXRpb25cbiAgICBwcml2YXRlIHJlYWRvbmx5IF9kb21haW46IHN0cmluZztcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9hcHBVUkw6IHN0cmluZztcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9zdG9yYWdlVXBkYXRlcjogKG5ld1Rva2VuOiBhbnkpID0+IHZvaWQ7XG4gICAgcHJpdmF0ZSByZWFkb25seSBfc3RvcmFnZVJlbW92ZXI6ICgpID0+IHZvaWQ7XG5cbiAgICAvLyBUaGlzIHR3byBwcm9wZXJ0aWVzIGFyZSB1c2VkIGZvciBpbnRlci13aW5kb3cgY29tbXVuY2F0aW9uLlxuICAgIC8vIEl0IGlzIGFjaGlldmUgdGhyb3VnaCB0aGUgdXBkYXRlIG9mIHRoZSBkdW1teSBrZXkgc3RvcmFnZSAnX2NvbW1LZXlOYW1lJ1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX2NvbW1LZXlOYW1lID0gJ0FuZ3VsYXJBYXBBdXRoVXBkYXRlZCc7XG4gICAgcHJpdmF0ZSByZWFkb25seSBfY29tbUtleVVwZGF0ZXIgPSAoKSA9PiBsb2NhbFN0b3JhZ2Uuc2V0SXRlbSh0aGlzLl9jb21tS2V5TmFtZSwgJycgKyBuZXcgRGF0ZSgpLmdldFRpbWUoKSk7XG5cbiAgICBjb25zdHJ1Y3RvcihcbiAgICAgICAgcHJpdmF0ZSBfcmVuZGVyZXJGYWN0b3J5OiBSZW5kZXJlckZhY3RvcnkyLFxuICAgICAgICBwcml2YXRlIF90b2tlblNlcnZpY2U6IFRva2VuU2VydmljZSxcbiAgICAgICAgQEluamVjdChBQVBfQ09ORklHKSBwcml2YXRlIGNvbmZpZzogQXV0aENvbmZpZ1xuICAgICkge1xuICAgICAgICB0aGlzLl9kb21haW4gPSBlbmNvZGVVUklDb21wb25lbnQod2luZG93LmxvY2F0aW9uLm9yaWdpbik7XG4gICAgICAgIHRoaXMuX2FwcFVSTCA9IGNvbmZpZy5hYXBVUkwucmVwbGFjZSgvXFwvJC8sICcnKTtcbiAgICAgICAgdGhpcy5fc3RvcmFnZVVwZGF0ZXIgPSBjb25maWcudG9rZW5VcGRhdGVyO1xuICAgICAgICBpZiAoY29uZmlnLnRva2VuUmVtb3Zlcikge1xuICAgICAgICAgICAgdGhpcy5fc3RvcmFnZVJlbW92ZXIgPSBjb25maWcudG9rZW5SZW1vdmVyO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5fc3RvcmFnZVJlbW92ZXIgPSAoKSA9PiBjb25maWcudG9rZW5VcGRhdGVyKG51bGwpO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgcmVuZGVyZXIgPSB0aGlzLl9yZW5kZXJlckZhY3RvcnkuY3JlYXRlUmVuZGVyZXIobnVsbCwgbnVsbCk7XG4gICAgICAgIHRoaXMuX2xpc3RlbkxvZ2luTWVzc2FnZShyZW5kZXJlcik7XG4gICAgICAgIHRoaXMuX2xpc3RlbkNoYW5nZXNGcm9tT3RoZXJXaW5kb3dzKHJlbmRlcmVyKTtcblxuICAgICAgICB0aGlzLl91cGRhdGVDcmVkZW50aWFscygpOyAvLyBUT0RPOiBleHBlcmltZW50IHdpdGggc2V0VGltZU91dFxuICAgIH1cblxuICAgIHB1YmxpYyBpc0F1dGhlbnRpY2F0ZWQoKTogT2JzZXJ2YWJsZSA8IGJvb2xlYW4gPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jcmVkZW50aWFscy5hc09ic2VydmFibGUoKS5waXBlKFxuICAgICAgICAgICAgbWFwKGNyZWRlbnRpYWxzID0+IGNyZWRlbnRpYWxzID8gdHJ1ZSA6IGZhbHNlKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIHB1YmxpYyBjcmVkZW50aWFscygpOiBPYnNlcnZhYmxlIDwgQ3JlZGVudGlhbHMgfCBudWxsID4ge1xuICAgICAgICByZXR1cm4gdGhpcy5fY3JlZGVudGlhbHMuYXNPYnNlcnZhYmxlKCk7XG4gICAgfVxuXG4gICAgcHVibGljIHJlYWxuYW1lKCk6IE9ic2VydmFibGUgPCBzdHJpbmcgfCBudWxsID4ge1xuICAgICAgICByZXR1cm4gdGhpcy5fY3JlZGVudGlhbHMuYXNPYnNlcnZhYmxlKCkucGlwZShcbiAgICAgICAgICAgIG1hcChjcmVkZW50aWFscyA9PiBjcmVkZW50aWFscyA/ICBjcmVkZW50aWFscy5yZWFsbmFtZSA6IG51bGwpXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcHVibGljIHVzZXJuYW1lKCk6IE9ic2VydmFibGUgPCBzdHJpbmcgfCBudWxsID4ge1xuICAgICAgICByZXR1cm4gdGhpcy5fY3JlZGVudGlhbHMuYXNPYnNlcnZhYmxlKCkucGlwZShcbiAgICAgICAgICAgIG1hcChjcmVkZW50aWFscyA9PiBjcmVkZW50aWFscyA/ICBjcmVkZW50aWFscy51c2VybmFtZSA6IG51bGwpXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcHVibGljIHRva2VuKCk6IE9ic2VydmFibGUgPCBzdHJpbmcgfCBudWxsID4ge1xuICAgICAgICByZXR1cm4gdGhpcy5fY3JlZGVudGlhbHMuYXNPYnNlcnZhYmxlKCkucGlwZShcbiAgICAgICAgICAgIG1hcChjcmVkZW50aWFscyA9PiBjcmVkZW50aWFscyA/ICBjcmVkZW50aWFscy50b2tlbiA6IG51bGwpXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRnVuY3Rpb25zIHRoYXQgb3BlbnMgYSB3aW5kb3cgaW5zdGVhZCBvZiBhIHRhYi5cbiAgICAgKlxuICAgICAqIFNlZSBtZXRob2QgX2ZpbHRlckxvZ2luT3B0aW9ucyByZWdhcmRpbmcgc2VjdXJpdHkgcmlza3Mgb2YgY2VydGFpblxuICAgICAqIExvZ2luT3B0aW9ucy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBsb2dpbk9wdGlvbnMgT3B0aW9ucyBwYXNzZWQgYXMgVVJMIHBhcmFtZXRlcnMgdG8gdGhlIFNTTy5cbiAgICAgKiBAcGFyYW0gd2lkdGggUGl4ZWwgd2lkdGggb2YgdGhlIGxvZ2luIHdpbmRvdy5cbiAgICAgKiBAcGFyYW0gaGVpZ2h0IFBpeGVsIGhlaWdodCBvZiB0aGUgbG9naW4gd2luZG93LlxuICAgICAqIEBwYXJhbSB0b3AgUG9zaXRpb24gb2YgdGhlIHRvcCBjb3JuZXJzLiBJZiBpdCBpcyBhIG5lZ2F0aXZlXG4gICAgICogICAgICAgICAgICAgbnVtYmVyIGl0IGNlbnRyZXMgdGhlIGxvZ2luIHdpbmRvdyBvbiB0aGUgc2NyZWVuLlxuICAgICAqIEBwYXJhbSBsZWZ0IFBvc2l0aW9uIG9mIHRoZSBsZWZ0IGNvcm5lcnMuIElmIGl0IGlzIGEgbmVnYXRpdmVcbiAgICAgKiAgICAgICAgICAgICBudW1iZXIgaXQgY2VudHJlcyB0aGUgbG9naW4gd2luZG93IG9uIHRoZSBzY3JlZW4uXG4gICAgICovXG4gICAgcHVibGljIHdpbmRvd09wZW4obG9naW5PcHRpb25zPzogTG9naW5PcHRpb25zLCB3aWR0aCA9IDY1MCwgaGVpZ2h0ID0gMTAwMCwgdG9wID0gLTEsIGxlZnQgPSAtMSkge1xuICAgICAgICBpZiAobGVmdCA8IDApIHtcbiAgICAgICAgICAgIGNvbnN0IHNjcmVlbldpZHRoID0gc2NyZWVuLndpZHRoO1xuICAgICAgICAgICAgaWYgKHNjcmVlbldpZHRoID4gd2lkdGgpIHtcbiAgICAgICAgICAgICAgICBsZWZ0ID0gTWF0aC5yb3VuZChzY3JlZW5XaWR0aCAvIDIgLSB3aWR0aCAvIDIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmICh0b3AgPCAwKSB7XG4gICAgICAgICAgICBjb25zdCBzY3JlZW5IZWlnaHQgPSBzY3JlZW4uaGVpZ2h0O1xuICAgICAgICAgICAgaWYgKHNjcmVlbkhlaWdodCA+IGhlaWdodCkge1xuICAgICAgICAgICAgICAgIHRvcCA9IE1hdGgucm91bmQoc2NyZWVuSGVpZ2h0IC8gMiAtIGhlaWdodCAvIDIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgY29uc3Qgd2luZG93T3B0aW9ucyA9IFtcbiAgICAgICAgICAgIGB3aWR0aD0ke3dpZHRofWAsXG4gICAgICAgICAgICBgaGVpZ2h0PSR7aGVpZ2h0fWAsXG4gICAgICAgICAgICBgbGVmdD0ke2xlZnR9YCxcbiAgICAgICAgICAgIGB0b3A9JHt0b3B9YCxcbiAgICAgICAgICAgICdwZXJzb25hbGJhcj1ubycsXG4gICAgICAgICAgICAndG9vbGJhcj1ubycsXG4gICAgICAgICAgICAnc2Nyb2xsYmFycz15ZXMnLFxuICAgICAgICAgICAgJ3Jlc2l6YWJsZT15ZXMnLFxuICAgICAgICAgICAgJ2RpcmVjdG9yaWVzPW5vJyxcbiAgICAgICAgICAgICdsb2NhdGlvbj1ubycsXG4gICAgICAgICAgICAnbWVudWJhcj1ubycsXG4gICAgICAgICAgICAndGl0bGViYXI9bm8nLFxuICAgICAgICAgICAgJ3Rvb2xiYXI9bm8nXG4gICAgICAgIF07XG5cbiAgICAgICAgY29uc3QgbG9naW5XaW5kb3cgPSB3aW5kb3cub3Blbih0aGlzLmdldFNTT1VSTChsb2dpbk9wdGlvbnMpLCAnU2lnbiBpbiB0byBFbGl4aXInLCB3aW5kb3dPcHRpb25zLmpvaW4oJywnKSk7XG4gICAgICAgIGlmIChsb2dpbldpbmRvdykge1xuICAgICAgICAgICAgbG9naW5XaW5kb3cuZm9jdXMoKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEZ1bmN0aW9ucyB0aGF0IG9wZW5zIGEgdGFiIChpbiBtb2Rlcm4gYnJvd3NlcikuXG4gICAgICpcbiAgICAgKiBTZWUgbWV0aG9kIF9maWx0ZXJMb2dpbk9wdGlvbnMgcmVnYXJkaW5nIHNlY3VyaXR5IHJpc2tzIG9mIGNlcnRhaW5cbiAgICAgKiBMb2dpbk9wdGlvbnMuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gbG9naW5PcHRpb25zIE9wdGlvbnMgcGFzc2VkIGFzIFVSTCBwYXJhbWV0ZXJzIHRvIHRoZSBTU08uXG4gICAgICovXG4gICAgcHVibGljIHRhYk9wZW4obG9naW5PcHRpb25zPzogTG9naW5PcHRpb25zKSB7XG4gICAgICAgIGNvbnN0IGxvZ2luV2luZG93ID0gd2luZG93Lm9wZW4odGhpcy5nZXRTU09VUkwobG9naW5PcHRpb25zKSwgJ1NpZ24gaW4gdG8gRWxpeGlyJyk7XG4gICAgICAgIGlmIChsb2dpbldpbmRvdykge1xuICAgICAgICAgICAgbG9naW5XaW5kb3cuZm9jdXMoKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFByb2R1Y2VzIGEgVVJMIHRoYXQgYWxsb3dzIGxvZ2dpbmcgaW50byB0aGUgc2luZ2xlIHNpZ24gb24gKFNTTykgcGFnZS5cbiAgICAgKiBUaGUgVVJMIGNhbnMgYmUgb3BlbmVkIGluIGEgbmV3IHRhYiB1c2luZyB0YXJnZXQ9XCJfYmxhbmtcIixcbiAgICAgKiBvciBpbiBhIG5ldyB3aW5kb3cgdXNpbmcgd2luZG93Lm9wZW4oKS5cbiAgICAgKlxuICAgICAqIFNlZSBtZXRob2QgX2ZpbHRlckxvZ2luT3B0aW9ucyByZWdhcmRpbmcgc2VjdXJpdHkgcmlza3Mgb2YgY2VydGFpblxuICAgICAqIExvZ2luT3B0aW9ucy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAgbG9naW5PcHRpb25zIE9wdGlvbnMgcGFzc2VkIGFzIFVSTCBwYXJhbWV0ZXJzIHRvIHRoZSBTU08uXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgU1NPIFVSTC5cbiAgICAgKlxuICAgICAqL1xuICAgICAgICBwdWJsaWMgZ2V0U1NPVVJMKG9wdGlvbnM/OiBMb2dpbk9wdGlvbnMpOiBzdHJpbmcge1xuICAgICAgICBsZXQgZXh0cmEgPSAnJztcbiAgICAgICAgaWYgKG9wdGlvbnMpIHtcbiAgICAgICAgICAgIHRoaXMuX2ZpbHRlckxvZ2luT3B0aW9ucyhvcHRpb25zKTtcbiAgICAgICAgICAgIGV4dHJhID0gT2JqZWN0LmtleXMob3B0aW9ucylcbiAgICAgICAgICAgICAgICAubWFwKGtleSA9PiBba2V5LCBvcHRpb25zW2tleV1dKVxuICAgICAgICAgICAgICAgIC5yZWR1Y2UoKGFjY3VtdWxhdG9yLCBrZXl2YWx1ZSkgPT4gYCR7YWNjdW11bGF0b3J9JiR7a2V5dmFsdWVbMF19PSR7a2V5dmFsdWVbMV19YCwgJycpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBgJHt0aGlzLl9hcHBVUkx9L3Nzbz9mcm9tPSR7dGhpcy5fZG9tYWlufSR7ZXh0cmF9YDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGaWx0ZXJzIG9wdGlvbnMgdGhhdCBhcmUgdW5zZWN1cmUuXG4gICAgICpcbiAgICAgKiBTZWUgdGhlIGFkdmFuY2Ugb3B0aW9ucyB0aGF0IGNhbiBiZSByZXF1ZXN0ZWQgdGhyb3VnaCB0aGUgb3B0aW9ucyBwYXJhbWV0ZXI6XG4gICAgICogaHR0cHM6Ly9hcGkuYWFpLmViaS5hYy51ay9kb2NzL2F1dGhlbnRpY2F0aW9uL2F1dGhlbnRpY2F0aW9uLmluZGV4Lmh0bWwjX2NvbW1vbl9hdHRyaWJ1dGVzXG4gICAgICpcbiAgICAgKiBUaGUgdGltZSB0byBsaXZlIHBhcmFtZW50ZXIgKHR0bCkgZGVmYXVsdCB2YWx1ZSBpcyA2MCBtaW51dGVzLiBJdCBpcyBhXG4gICAgICogYmlnIHNlY3VyaXR5IHJpc2sgdG8gcmVxdWVzdCBsb25nZXIgdHRsLiBJZiBhIHRoaXJkIHBhcnR5IGdldHMgaG9sZCBvZlxuICAgICAqIHN1Y2ggdG9rZW4sIG1lYW5zIHRoYXQgdGhleSBjb3VsZCB1c2UgaXQgZm9yIGEgZGF5LCB3ZWVrLCB5ZWFyXG4gICAgICogKGVzc2VudGlhbGx5LCBsaWtlIGhhdmluZyB0aGUgdXNlcm5hbWUvcGFzc3dvcmQpLlxuICAgICAqXG4gICAgICogQHBhcmFtICBsb2dpbk9wdGlvbnMgT3B0aW9ucyBwYXNzZWQgYXMgVVJMIHBhcmFtZXRlcnMgdG8gdGhlIFNTTy5cbiAgICAgKlxuICAgICAqXG4gICAgICovXG4gICAgcHVibGljIF9maWx0ZXJMb2dpbk9wdGlvbnMob3B0aW9uczogTG9naW5PcHRpb25zKSB7XG4gICAgICAgIGlmIChPYmplY3Qua2V5cyhvcHRpb25zKS5pbmRleE9mKCd0dGwnKSA+IC0xKSB7XG4gICAgICAgICAgICBjb25zdCB0dGw6IG51bWJlciA9ICtvcHRpb25zWyd0dGwnXTtcbiAgICAgICAgICAgIGNvbnN0IHNvZnRMaW1pdCA9IDYwO1xuICAgICAgICAgICAgY29uc3QgaGFyZExpbWl0ID0gNjAgKiAyNDtcbiAgICAgICAgICAgIGlmICh0dGwgPiBoYXJkTGltaXQpIHtcbiAgICAgICAgICAgICAgICB3aW5kb3cuY29uc29sZS5lcnJvcihgTG9naW4gcmVxdWVzdGVkIHdpdGggYW4gZXhwaXJhdGlvbiBsb25nZXIgdGhhbiAke2hhcmRMaW1pdH0gbWludXRlcyEgVGhpcyBpcyBub3QgYWxsb3dlZC5gKTtcbiAgICAgICAgICAgICAgICB3aW5kb3cuY29uc29sZS5lcnJvcihgRXhwaXJhdGlvbiByZXF1ZXN0IHJlc2V0IHRvICR7aGFyZExpbWl0fSBtaW51dGVzLmApO1xuICAgICAgICAgICAgICAgIG9wdGlvbnNbJ3R0bCddID0gJycgKyBoYXJkTGltaXQ7XG4gICAgICAgICAgICB9IGVsc2UgaWYgKHR0bCA+IHNvZnRMaW1pdCkge1xuICAgICAgICAgICAgICAgIHdpbmRvdy5jb25zb2xlLndhcm4oYExvZ2luIHJlcXVlc3RlZCB3aXRoIGFuIGV4cGlyYXRpb24gbG9uZ2VyIHRoYW4gJHtzb2Z0TGltaXR9IG1pbnV0ZXMhYCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGdW5jdGlvbnMgdGhhdCBsb2dzIG91dCB0aGUgdXNlci5cbiAgICAgKiBJdCB0cmlnZ2VycyB0aGUgbG9nb3V0IGNhbGxiYWNrcy5cbiAgICAgKiBJdCBpcyBhbiBhcnJvdyBmdW5jdGlvbiAobGFtYmRhKSBiZWNhdXNlIGluIHRoYXQgd2F5IGl0IGhhcyBhIHJlZmVyZW5jZVxuICAgICAqIHRvICd0aGlzJyB3aGVuIHVzZWQgaW4gc2V0VGltZW91dCBjYWxsLlxuICAgICAqL1xuICAgIHB1YmxpYyBsb2dPdXQoKSB7XG4gICAgICAgIHRoaXMuX3N0b3JhZ2VSZW1vdmVyKCk7XG4gICAgICAgIHRoaXMuX3VwZGF0ZUNyZWRlbnRpYWxzKCk7XG5cbiAgICAgICAgLy8gVHJpZ2dlcnMgdXBkYXRpbmcgb3RoZXIgd2luZG93c1xuICAgICAgICB0aGlzLl9jb21tS2V5VXBkYXRlcigpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEFkZCBhIGNhbGxiYWNrIHRvIHRoZSBMb2dJbiBldmVudC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBjYWxsYmFjayBUaGUgRnVuY3Rpb24gY2FsbGVkIHdoZW4gdGhlIGxvZ2luIGV2ZW50IGlzIHRyaWdnZXJlZCBhbmQgdGhlXG4gICAgICogICAgSldUIHRva2VuIGlzIHJlY2VpdmVkIGFuZCBhY2NlcHRlZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBldmVudCByZWdpc3RyYXRpb24gaWQgKG5lY2Vzc2FyeSB0byB1bnJlZ2lzdGVyIHRoZSBldmVudCkuXG4gICAgICovXG4gICAgcHVibGljIGFkZExvZ0luRXZlbnRMaXN0ZW5lcihjYWxsYmFjazogRnVuY3Rpb24pOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gdGhpcy5fbG9naW5DYWxsYmFja3MucHVzaChjYWxsYmFjayk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlIGEgY2FsbGJhY2sgZnJvbSB0aGUgTG9nSW4gZXZlbnQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gaWQgVGhlIGlkIGdpdmVuIHdoZW4gZXZlbnQgbGlzdGVuZXIgd2FzIGFkZGVkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgdHJ1ZSB3aGVuIHJlbW92ZSBzdWNjZXNzZnVsbHksIGZhbHNlIG90aGVyd2lzZS5cbiAgICAgKi9cbiAgICBwdWJsaWMgcmVtb3ZlTG9nSW5FdmVudExpc3RlbmVyKGlkOiBudW1iZXIpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuIGRlbGV0ZSB0aGlzLl9sb2dpbkNhbGxiYWNrc1tpZCAtIDFdO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEFkZCBhIGNhbGxiYWNrIHRvIHRoZSBMb2dPdXQgZXZlbnQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gY2FsbGJhY2sgVGhlIEZ1bmN0aW9uIGNhbGxlZCB3aGVuIHRoZSBsb2dvdXQgZXZlbnQgaXMgdHJpZ2dlcmVkIGFuZCB0aGVcbiAgICAgKiAgICBKV1QgdG9rZW4gaXMgcmVjZWl2ZWQgYW5kIGFjY2VwdGVkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIHJlZ2lzdHJhdGlvbiBpZCAobmVjZXNzYXJ5IHRvIHVucmVnaXN0ZXIgdGhlIGV2ZW50KS5cbiAgICAgKi9cbiAgICBwdWJsaWMgYWRkTG9nT3V0RXZlbnRMaXN0ZW5lcihjYWxsYmFjazogRnVuY3Rpb24pOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gdGhpcy5fbG9nb3V0Q2FsbGJhY2tzLnB1c2goY2FsbGJhY2spO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbW92ZSBhIGNhbGxiYWNrIGZyb20gdGhlIExvZ091dCBldmVudC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBpZCBUaGUgaWQgZ2l2ZW4gd2hlbiBldmVudCBsaXN0ZW5lciB3YXMgYWRkZWQuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyB0cnVlIHdoZW4gcmVtb3ZlIHN1Y2Nlc3NmdWxseSwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgICAqL1xuICAgIHB1YmxpYyByZW1vdmVMb2dPdXRFdmVudExpc3RlbmVyKGlkOiBudW1iZXIpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuIGRlbGV0ZSB0aGlzLl9sb2dvdXRDYWxsYmFja3NbaWQgLSAxXTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBMaXN0ZW4gZm9yIGxvZ2luIG1lc3NhZ2VzIGZyb20gb3RoZXIgd2luZG93cy5cbiAgICAgKiBUaGVzZSBtZXNzYWdlcyBjb250YWluIHRoZSB0b2tlbnMgZnJvbSB0aGUgQUFQLlxuICAgICAqIElmIGEgdG9rZW4gaXMgcmVjZWl2ZWQgdGhlbiB0aGUgY2FsbGJhY2tzIGFyZSB0cmlnZ2VyZWQuXG4gICAgICovXG4gICAgcHJpdmF0ZSBfbGlzdGVuTG9naW5NZXNzYWdlKHJlbmRlcmVyOiBSZW5kZXJlcjIpIHtcbiAgICAgICAgcmVuZGVyZXIubGlzdGVuKCd3aW5kb3cnLCAnbWVzc2FnZScsIChldmVudDogTWVzc2FnZUV2ZW50KSA9PiB7XG4gICAgICAgICAgICBpZiAoIXRoaXMubWVzc2FnZUlzQWNjZXB0YWJsZShldmVudCkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0aGlzLl9zdG9yYWdlVXBkYXRlcihldmVudC5kYXRhKTtcbiAgICAgICAgICAgIGV2ZW50LnNvdXJjZS5jbG9zZSgpO1xuICAgICAgICAgICAgdGhpcy5fdXBkYXRlQ3JlZGVudGlhbHMoKTtcblxuICAgICAgICAgICAgLy8gVHJpZ2dlcnMgdXBkYXRpbmcgb3RoZXIgd2luZG93c1xuICAgICAgICAgICAgdGhpcy5fY29tbUtleVVwZGF0ZXIoKTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqIExpc3RlbiB0byBjaGFuZ2VzIGluIHRoZSB0b2tlbiBmcm9tICpvdGhlciogd2luZG93cy5cbiAgICAgKlxuICAgICAqIEZvciBpbnRlci13aW5kb3cgY29tbXVuaWNhdGlvbiBtZXNzYWdlcyBhcmUgdHJhbnNtaXR0ZWQgdHJvdWdoIGNoYW5nZXNcbiAgICAgKiBvbiBhIGR1bW15IHN0b3JhZ2Uga2V5IHByb3BlcnR5OiAnX2NvbW1LZXlOYW1lJy5cbiAgICAgKlxuICAgICAqIE5vdGljZSB0aGF0IGNoYW5nZXMgaW4gdGhlICdfY29tbUtleU5hbWUnIHByb2R1Y2VkIGJ5IHRoaXMgY2xhc3MgZG9lc24ndFxuICAgICAqIHRyaWdnZXIgdGhpcyBldmVudC5cbiAgICAgKi9cbiAgICBwcml2YXRlIF9saXN0ZW5DaGFuZ2VzRnJvbU90aGVyV2luZG93cyhyZW5kZXJlcjogUmVuZGVyZXIyKSB7XG4gICAgICAgIHJlbmRlcmVyLmxpc3Rlbignd2luZG93JywgJ3N0b3JhZ2UnLCAoZXZlbnQ6IFN0b3JhZ2VFdmVudCkgPT4ge1xuICAgICAgICAgICAgaWYgKGV2ZW50LmtleSA9PT0gdGhpcy5fY29tbUtleU5hbWUpIHtcbiAgICAgICAgICAgICAgICB0aGlzLl91cGRhdGVDcmVkZW50aWFscygpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDaGVjayBpZiB0aGUgbWVzc2FnZSBpcyBjb21pbmcgZnJvbSB0aGUgc2FtZSBkb21haW4gd2UgdXNlIHRvIGdlbmVyYXRlXG4gICAgICogdGhlIFNTTyBVUkwsIG90aGVyd2lzZSBpdCdzIGlmZnkgYW5kIHNob3VsZG4ndCB0cnVzdCBpdC5cbiAgICAgKi9cbiAgICBwcml2YXRlIG1lc3NhZ2VJc0FjY2VwdGFibGUoZXZlbnQ6IE1lc3NhZ2VFdmVudCk6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gZXZlbnQub3JpZ2luID09PSB0aGlzLl9hcHBVUkw7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBfdXBkYXRlQ3JlZGVudGlhbHMoKSB7XG4gICAgICAgIGNvbnN0IGlzQXV0aGVudGljYXRlZCA9IHRoaXMuX2xvZ2dlZEluKCk7XG5cbiAgICAgICAgaWYgKHRoaXMuX3RpbWVvdXRJRCkge1xuICAgICAgICAgICAgd2luZG93LmNsZWFyVGltZW91dCh0aGlzLl90aW1lb3V0SUQpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGlzQXV0aGVudGljYXRlZCkge1xuICAgICAgICAgICAgdGhpcy5fY3JlZGVudGlhbHMubmV4dCh7XG4gICAgICAgICAgICAgICAgcmVhbG5hbWU6IDwgc3RyaW5nID4gdGhpcy5fZ2V0UmVhbE5hbWUoKSxcbiAgICAgICAgICAgICAgICB1c2VybmFtZTogPCBzdHJpbmcgPiB0aGlzLl9nZXRVc2VyTmFtZSgpLFxuICAgICAgICAgICAgICAgIHRva2VuOiA8IHN0cmluZyA+IHRoaXMuX2dldFRva2VuKClcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICB0aGlzLl9sb2dpbkNhbGxiYWNrcy5tYXAoY2FsbGJhY2sgPT4gY2FsbGJhY2sgJiYgY2FsbGJhY2soKSk7XG5cbiAgICAgICAgICAgIC8vIFNjaGVkdWxlIGZ1dHVyZSBsb2dvdXQgZXZlbnQgYmFzZSBvbiB0b2tlbiBleHBpcmF0aW9uXG4gICAgICAgICAgICBjb25zdCBleHBpcmVEYXRlID0gPCBEYXRlID4gdGhpcy5fdG9rZW5TZXJ2aWNlLmdldFRva2VuRXhwaXJhdGlvbkRhdGUoKTtcbiAgICAgICAgICAgIC8vIENvZXJjaW5nIGRhdGVzIHRvIG51bWJlcnMgd2l0aCB0aGUgdW5hcnkgb3BlcmF0b3IgJysnXG4gICAgICAgICAgICBjb25zdCBkZWxheSA9ICtleHBpcmVEYXRlIC0gK25ldyBEYXRlKCk7XG4gICAgICAgICAgICB0aGlzLl90aW1lb3V0SUQgPSB3aW5kb3cuc2V0VGltZW91dCgoKSA9PiB0aGlzLmxvZ091dCgpLCBkZWxheSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aGlzLl9zdG9yYWdlUmVtb3ZlcigpOyAvLyBDbGVhbnVwIHBvc3NpYmxlIGxlZnQgYmVoaW5kIHRva2VuXG4gICAgICAgICAgICB0aGlzLl9jcmVkZW50aWFscy5uZXh0KG51bGwpO1xuICAgICAgICAgICAgdGhpcy5fbG9nb3V0Q2FsbGJhY2tzLm1hcChjYWxsYmFjayA9PiBjYWxsYmFjayAmJiBjYWxsYmFjaygpKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENoZWNrIGlmIHRoZXJlJ3MgYSB1c2VyIGxvZ2dpbmcgb24gYW5kIHdoZXRoZXIgdGhlIHRva2VuIGlzIHN0aWxsIHZhbGlkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgIFdoZXRoZXIgdGhlIHVzZXIgdXNlciBpcyBhdXRoZW50aWNhdGVkIG9yIG5vdC5cbiAgICAgKi9cbiAgICBwcml2YXRlIF9sb2dnZWRJbigpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3Rva2VuU2VydmljZS5pc1Rva2VuVmFsaWQoKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIF9nZXRUb2tlbigpOiBzdHJpbmcgfCBudWxsIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3Rva2VuU2VydmljZS5nZXRUb2tlbigpO1xuICAgIH1cblxuICAgIHByaXZhdGUgX2dldFVzZXJOYW1lKCk6IHN0cmluZyB8IG51bGwge1xuICAgICAgICByZXR1cm4gdGhpcy5fdG9rZW5TZXJ2aWNlLmdldENsYWltIDwgc3RyaW5nLCBudWxsID4gKCdlbWFpbCcsIG51bGwpO1xuICAgIH1cblxuICAgIHByaXZhdGUgX2dldFJlYWxOYW1lKCk6IHN0cmluZyB8IG51bGwge1xuICAgICAgICByZXR1cm4gdGhpcy5fdG9rZW5TZXJ2aWNlLmdldENsYWltIDwgc3RyaW5nLCBudWxsID4gKCduYW1lJywgbnVsbCk7XG4gICAgfVxuXG59XG4iLCJpbXBvcnQge1xuICAgIE5nTW9kdWxlLFxuICAgIE9wdGlvbmFsLFxuICAgIFNraXBTZWxmLFxuICAgIE1vZHVsZVdpdGhQcm92aWRlcnMsXG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuXG5pbXBvcnQge1xuICAgIEpXVF9PUFRJT05TLFxuICAgIEp3dEhlbHBlclNlcnZpY2Vcbn0gZnJvbSAnQGF1dGgwL2FuZ3VsYXItand0JztcbmltcG9ydCB7XG4gICAgVG9rZW5TZXJ2aWNlXG59IGZyb20gJy4vdG9rZW4uc2VydmljZSc7XG5pbXBvcnQge1xuICAgIEF1dGhDb25maWcsXG4gICAgQUFQX0NPTkZJRyxcbiAgICBERUZBVUxUX0NPTkZcbn0gZnJvbSAnLi9hdXRoLmNvbmZpZyc7XG5pbXBvcnQge1xuICAgIEF1dGhTZXJ2aWNlXG59IGZyb20gJy4vYXV0aC5zZXJ2aWNlJztcblxuQE5nTW9kdWxlKHt9KVxuZXhwb3J0IGNsYXNzIEF1dGhNb2R1bGUge1xuXG4gICAgY29uc3RydWN0b3IoQE9wdGlvbmFsKCkgQFNraXBTZWxmKCkgcGFyZW50TW9kdWxlOiBBdXRoTW9kdWxlKSB7XG4gICAgICAgIGlmIChwYXJlbnRNb2R1bGUpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignQXV0aE1vZHVsZSBpcyBhbHJlYWR5IGxvYWRlZC4gSXQgc2hvdWxkIG9ubHkgYmUgaW1wb3J0ZWQgaW4geW91ciBhcHBsaWNhdGlvblxcJ3MgbWFpbiBtb2R1bGUuJyk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBzdGF0aWMgZm9yUm9vdChvcHRpb25zPzogQXV0aENvbmZpZyk6IE1vZHVsZVdpdGhQcm92aWRlcnMge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgbmdNb2R1bGU6IEF1dGhNb2R1bGUsXG4gICAgICAgICAgICBwcm92aWRlcnM6IFtcbiAgICAgICAgICAgICAgICBUb2tlblNlcnZpY2UsXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICBwcm92aWRlOiBBQVBfQ09ORklHLFxuICAgICAgICAgICAgICAgICAgICB1c2VWYWx1ZTogb3B0aW9ucyA/IG9wdGlvbnMgOiBERUZBVUxUX0NPTkZcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIEF1dGhTZXJ2aWNlXG4gICAgICAgICAgICBdXG4gICAgICAgIH07XG4gICAgfVxufVxuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBOzs7Ozs7QUFlQTs7OztJQUVJLFlBQ1k7UUFBQSxTQUFJLEdBQUosSUFBSTtLQUNaOzs7O0lBRUcsUUFBUTtRQUNYLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQzs7Ozs7SUFHNUIsc0JBQXNCO1FBQ3pCLElBQUk7WUFDQSxPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsc0JBQXNCLEVBQUUsQ0FBQztTQUM3QztRQUFDLHdCQUFPLENBQUMsRUFBRTtZQUNSLE9BQU8sSUFBSSxDQUFDO1NBQ2Y7Ozs7O0lBR0UsWUFBWTtRQUNmLElBQUk7WUFDQSxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztTQUN0QztRQUFDLHdCQUFPLEtBQUssRUFBRTtZQUNaLE9BQU8sS0FBSyxDQUFDO1NBQ2hCOzs7Ozs7Ozs7OztJQVdFLFFBQVEsQ0FBVyxLQUFhLEVBQUUsWUFBZTtRQUNwRCxJQUFJO1lBQ0EsdUJBQU0sS0FBSyxxQkFBUyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFBLENBQUM7WUFDbkQsSUFBSSxLQUFL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