UNPKG

angular-aap-auth

Version:

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

547 lines (546 loc) 46.1 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ import { Injectable, Inject, RendererFactory2 } from '@angular/core'; import { BehaviorSubject, } from 'rxjs'; import { map } from 'rxjs/operators'; import { AAP_CONFIG } from './auth.config'; import { TokenService } from './token.service'; /** * @record */ export function LoginOptions() { } function LoginOptions_tsickle_Closure_declarations() { /* TODO: handle strange member: [key: string]: string; */ } /** * @record */ export function Credentials() { } function Credentials_tsickle_Closure_declarations() { /** @type {?} */ Credentials.prototype.realname; /** @type {?} */ Credentials.prototype.username; /** @type {?} */ Credentials.prototype.token; } var AuthService = /** @class */ (function () { function AuthService(_rendererFactory, _tokenService, config) { var _this = this; 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 = function () { return 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 = function () { return config.tokenUpdater(null); }; } var /** @type {?} */ renderer = this._rendererFactory.createRenderer(null, null); this._listenLoginMessage(renderer); this._listenChangesFromOtherWindows(renderer); this._updateCredentials(); // TODO: experiment with setTimeOut } /** * @return {?} */ AuthService.prototype.isAuthenticated = /** * @return {?} */ function () { return this._credentials.asObservable().pipe(map(function (credentials) { return credentials ? true : false; })); }; /** * @return {?} */ AuthService.prototype.credentials = /** * @return {?} */ function () { return this._credentials.asObservable(); }; /** * @return {?} */ AuthService.prototype.realname = /** * @return {?} */ function () { return this._credentials.asObservable().pipe(map(function (credentials) { return credentials ? credentials.realname : null; })); }; /** * @return {?} */ AuthService.prototype.username = /** * @return {?} */ function () { return this._credentials.asObservable().pipe(map(function (credentials) { return credentials ? credentials.username : null; })); }; /** * @return {?} */ AuthService.prototype.token = /** * @return {?} */ function () { return this._credentials.asObservable().pipe(map(function (credentials) { return 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 {?} */ AuthService.prototype.windowOpen = /** * 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 {?} */ function (loginOptions, width, height, top, left) { if (width === void 0) { width = 650; } if (height === void 0) { height = 1000; } if (top === void 0) { top = -1; } if (left === void 0) { left = -1; } if (left < 0) { var /** @type {?} */ screenWidth = screen.width; if (screenWidth > width) { left = Math.round(screenWidth / 2 - width / 2); } } if (top < 0) { var /** @type {?} */ screenHeight = screen.height; if (screenHeight > height) { top = Math.round(screenHeight / 2 - height / 2); } } var /** @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' ]; var /** @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 {?} */ AuthService.prototype.tabOpen = /** * 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 {?} */ function (loginOptions) { var /** @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. * */ AuthService.prototype.getSSOURL = /** * 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. * */ function (options) { var /** @type {?} */ extra = ''; if (options) { this._filterLoginOptions(options); extra = Object.keys(options) .map(function (key) { return [key, options[key]]; }) .reduce(function (accumulator, keyvalue) { return 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 {?} */ AuthService.prototype._filterLoginOptions = /** * 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 {?} */ function (options) { if (Object.keys(options).indexOf('ttl') > -1) { var /** @type {?} */ ttl = +options['ttl']; var /** @type {?} */ softLimit = 60; var /** @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 {?} */ AuthService.prototype.logOut = /** * 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 {?} */ function () { 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). */ AuthService.prototype.addLogInEventListener = /** * 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). */ function (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. */ AuthService.prototype.removeLogInEventListener = /** * Remove a callback from the LogIn event. * * @param {?} id The id given when event listener was added. * * @return {?} true when remove successfully, false otherwise. */ function (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). */ AuthService.prototype.addLogOutEventListener = /** * 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). */ function (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. */ AuthService.prototype.removeLogOutEventListener = /** * Remove a callback from the LogOut event. * * @param {?} id The id given when event listener was added. * * @return {?} true when remove successfully, false otherwise. */ function (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 {?} */ AuthService.prototype._listenLoginMessage = /** * 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 {?} */ function (renderer) { var _this = this; renderer.listen('window', 'message', function (event) { if (!_this.messageIsAcceptable(event)) { return; } _this._storageUpdater(event.data); event.source.close(); _this._updateCredentials(); // Triggers updating other windows // 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 {?} */ AuthService.prototype._listenChangesFromOtherWindows = /** * 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 {?} */ function (renderer) { var _this = this; renderer.listen('window', 'storage', function (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 {?} */ AuthService.prototype.messageIsAcceptable = /** * 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 {?} */ function (event) { return event.origin === this._appURL; }; /** * @return {?} */ AuthService.prototype._updateCredentials = /** * @return {?} */ function () { var _this = this; var /** @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(function (callback) { return callback && callback(); }); // Schedule future logout event base on token expiration var /** @type {?} */ expireDate = /** @type {?} */ (this._tokenService.getTokenExpirationDate()); // Coercing dates to numbers with the unary operator '+' var /** @type {?} */ delay = +expireDate - +new Date(); this._timeoutID = window.setTimeout(function () { return _this.logOut(); }, delay); } else { this._storageRemover(); // Cleanup possible left behind token this._credentials.next(null); this._logoutCallbacks.map(function (callback) { return 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. */ AuthService.prototype._loggedIn = /** * Check if there's a user logging on and whether the token is still valid. * * @return {?} Whether the user user is authenticated or not. */ function () { return this._tokenService.isTokenValid(); }; /** * @return {?} */ AuthService.prototype._getToken = /** * @return {?} */ function () { return this._tokenService.getToken(); }; /** * @return {?} */ AuthService.prototype._getUserName = /** * @return {?} */ function () { return this._tokenService.getClaim('email', null); }; /** * @return {?} */ AuthService.prototype._getRealName = /** * @return {?} */ function () { return this._tokenService.getClaim('name', null); }; AuthService.decorators = [ { type: Injectable }, ]; /** @nocollapse */ AuthService.ctorParameters = function () { return [ { type: RendererFactory2 }, { type: TokenService }, { type: undefined, decorators: [{ type: Inject, args: [AAP_CONFIG,] }] } ]; }; return AuthService; }()); export { AuthService }; function AuthService_tsickle_Closure_declarations() { /** @type {?} */ AuthService.prototype._credentials; /** @type {?} */ AuthService.prototype._loginCallbacks; /** @type {?} */ AuthService.prototype._logoutCallbacks; /** @type {?} */ AuthService.prototype._timeoutID; /** @type {?} */ AuthService.prototype._domain; /** @type {?} */ AuthService.prototype._appURL; /** @type {?} */ AuthService.prototype._storageUpdater; /** @type {?} */ AuthService.prototype._storageRemover; /** @type {?} */ AuthService.prototype._commKeyName; /** @type {?} */ AuthService.prototype._commKeyUpdater; /** @type {?} */ AuthService.prototype._rendererFactory; /** @type {?} */ AuthService.prototype._tokenService; /** @type {?} */ AuthService.prototype.config; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6Im5nOi8vYW5ndWxhci1hYXAtYXV0aC8iLCJzb3VyY2VzIjpbImFwcC9tb2R1bGVzL2F1dGgvYXV0aC5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7QUFBQSxPQUFPLEVBQ0gsVUFBVSxFQUNWLE1BQU0sRUFDTixnQkFBZ0IsRUFFbkIsTUFBTSxlQUFlLENBQUM7QUFJdkIsT0FBTyxFQUNILGVBQWUsR0FDbEIsTUFBTSxNQUFNLENBQUM7QUFDZCxPQUFPLEVBQ0gsR0FBRyxFQUNOLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEIsT0FBTyxFQUNILFVBQVUsRUFFYixNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQ0gsWUFBWSxFQUNmLE1BQU0saUJBQWlCLENBQUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0lBaUNyQixxQkFDWSxrQkFDQSxlQUNvQixNQUFrQjtRQUhsRCxpQkFtQkM7UUFsQlcscUJBQWdCLEdBQWhCLGdCQUFnQjtRQUNoQixrQkFBYSxHQUFiLGFBQWE7UUFDTyxXQUFNLEdBQU4sTUFBTSxDQUFZOzRCQXJCM0IsSUFBSSxlQUFlLENBQXlCLElBQUksQ0FBQzsrQkFFbEMsRUFBRTtnQ0FDRCxFQUFFOzBCQUVMLElBQUk7NEJBVVIsdUJBQXVCOytCQUNwQixjQUFNLE9BQUEsWUFBWSxDQUFDLE9BQU8sQ0FBQyxLQUFJLENBQUMsWUFBWSxFQUFFLEVBQUUsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDLEVBQWxFLENBQWtFO1FBT3ZHLElBQUksQ0FBQyxPQUFPLEdBQUcsa0JBQWtCLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMxRCxJQUFJLENBQUMsT0FBTyxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNoRCxJQUFJLENBQUMsZUFBZSxHQUFHLE1BQU0s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b3J0IHtcbiAgICBJbmplY3RhYmxlLFxuICAgIEluamVjdCxcbiAgICBSZW5kZXJlckZhY3RvcnkyLFxuICAgIFJlbmRlcmVyMlxufSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7XG4gICAgT2JzZXJ2YWJsZVxufSBmcm9tICdyeGpzJztcbmltcG9ydCB7XG4gICAgQmVoYXZpb3JTdWJqZWN0LFxufSBmcm9tICdyeGpzJztcbmltcG9ydCB7XG4gICAgbWFwXG59IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcblxuaW1wb3J0IHtcbiAgICBBQVBfQ09ORklHLFxuICAgIEF1dGhDb25maWdcbn0gZnJvbSAnLi9hdXRoLmNvbmZpZyc7XG5pbXBvcnQge1xuICAgIFRva2VuU2VydmljZVxufSBmcm9tICcuL3Rva2VuLnNlcnZpY2UnO1xuXG5leHBvcnQgaW50ZXJmYWNlIExvZ2luT3B0aW9ucyB7XG4gICAgW2tleTogc3RyaW5nXTogc3RyaW5nO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIENyZWRlbnRpYWxzIHtcbiAgICByZWFsbmFtZTogc3RyaW5nO1xuICAgIHVzZXJuYW1lOiBzdHJpbmc7XG4gICAgdG9rZW46IHN0cmluZztcbn1cblxuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIEF1dGhTZXJ2aWNlIHtcblxuICAgIHByaXZhdGUgX2NyZWRlbnRpYWxzID0gbmV3IEJlaGF2aW9yU3ViamVjdCA8IENyZWRlbnRpYWxzIHwgbnVsbCA+IChudWxsKTtcblxuICAgIHByaXZhdGUgX2xvZ2luQ2FsbGJhY2tzOiBGdW5jdGlvbltdID0gW107XG4gICAgcHJpdmF0ZSBfbG9nb3V0Q2FsbGJhY2tzOiBGdW5jdGlvbltdID0gW107XG5cbiAgICBwcml2YXRlIF90aW1lb3V0SUQ6IG51bWJlciB8IG51bGwgPSBudWxsO1xuXG4gICAgLy8gQ29uZmlndXJhdGlvblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX2RvbWFpbjogc3RyaW5nO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX2FwcFVSTDogc3RyaW5nO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX3N0b3JhZ2VVcGRhdGVyOiAobmV3VG9rZW46IGFueSkgPT4gdm9pZDtcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9zdG9yYWdlUmVtb3ZlcjogKCkgPT4gdm9pZDtcblxuICAgIC8vIFRoaXMgdHdvIHByb3BlcnRpZXMgYXJlIHVzZWQgZm9yIGludGVyLXdpbmRvdyBjb21tdW5jYXRpb24uXG4gICAgLy8gSXQgaXMgYWNoaWV2ZSB0aHJvdWdoIHRoZSB1cGRhdGUgb2YgdGhlIGR1bW15IGtleSBzdG9yYWdlICdfY29tbUtleU5hbWUnXG4gICAgcHJpdmF0ZSByZWFkb25seSBfY29tbUtleU5hbWUgPSAnQW5ndWxhckFhcEF1dGhVcGRhdGVkJztcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9jb21tS2V5VXBkYXRlciA9ICgpID0+IGxvY2FsU3RvcmFnZS5zZXRJdGVtKHRoaXMuX2NvbW1LZXlOYW1lLCAnJyArIG5ldyBEYXRlKCkuZ2V0VGltZSgpKTtcblxuICAgIGNvbnN0cnVjdG9yKFxuICAgICAgICBwcml2YXRlIF9yZW5kZXJlckZhY3Rvcnk6IFJlbmRlcmVyRmFjdG9yeTIsXG4gICAgICAgIHByaXZhdGUgX3Rva2VuU2VydmljZTogVG9rZW5TZXJ2aWNlLFxuICAgICAgICBASW5qZWN0KEFBUF9DT05GSUcpIHByaXZhdGUgY29uZmlnOiBBdXRoQ29uZmlnXG4gICAgKSB7XG4gICAgICAgIHRoaXMuX2RvbWFpbiA9IGVuY29kZVVSSUNvbXBvbmVudCh3aW5kb3cubG9jYXRpb24ub3JpZ2luKTtcbiAgICAgICAgdGhpcy5fYXBwVVJMID0gY29uZmlnLmFhcFVSTC5yZXBsYWNlKC9cXC8kLywgJycpO1xuICAgICAgICB0aGlzLl9zdG9yYWdlVXBkYXRlciA9IGNvbmZpZy50b2tlblVwZGF0ZXI7XG4gICAgICAgIGlmIChjb25maWcudG9rZW5SZW1vdmVyKSB7XG4gICAgICAgICAgICB0aGlzLl9zdG9yYWdlUmVtb3ZlciA9IGNvbmZpZy50b2tlblJlbW92ZXI7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aGlzLl9zdG9yYWdlUmVtb3ZlciA9ICgpID0+IGNvbmZpZy50b2tlblVwZGF0ZXIobnVsbCk7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCByZW5kZXJlciA9IHRoaXMuX3JlbmRlcmVyRmFjdG9yeS5jcmVhdGVSZW5kZXJlcihudWxsLCBudWxsKTtcbiAgICAgICAgdGhpcy5fbGlzdGVuTG9naW5NZXNzYWdlKHJlbmRlcmVyKTtcbiAgICAgICAgdGhpcy5fbGlzdGVuQ2hhbmdlc0Zyb21PdGhlcldpbmRvd3MocmVuZGVyZXIpO1xuXG4gICAgICAgIHRoaXMuX3VwZGF0ZUNyZWRlbnRpYWxzKCk7IC8vIFRPRE86IGV4cGVyaW1lbnQgd2l0aCBzZXRUaW1lT3V0XG4gICAgfVxuXG4gICAgcHVibGljIGlzQXV0aGVudGljYXRlZCgpOiBPYnNlcnZhYmxlIDwgYm9vbGVhbiA+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2NyZWRlbnRpYWxzLmFzT2JzZXJ2YWJsZSgpLnBpcGUoXG4gICAgICAgICAgICBtYXAoY3JlZGVudGlhbHMgPT4gY3JlZGVudGlhbHMgPyB0cnVlIDogZmFsc2UpXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcHVibGljIGNyZWRlbnRpYWxzKCk6IE9ic2VydmFibGUgPCBDcmVkZW50aWFscyB8IG51bGwgPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jcmVkZW50aWFscy5hc09ic2VydmFibGUoKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgcmVhbG5hbWUoKTogT2JzZXJ2YWJsZSA8IHN0cmluZyB8IG51bGwgPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jcmVkZW50aWFscy5hc09ic2VydmFibGUoKS5waXBlKFxuICAgICAgICAgICAgbWFwKGNyZWRlbnRpYWxzID0+IGNyZWRlbnRpYWxzID8gIGNyZWRlbnRpYWxzLnJlYWxuYW1lIDogbnVsbClcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgdXNlcm5hbWUoKTogT2JzZXJ2YWJsZSA8IHN0cmluZyB8IG51bGwgPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jcmVkZW50aWFscy5hc09ic2VydmFibGUoKS5waXBlKFxuICAgICAgICAgICAgbWFwKGNyZWRlbnRpYWxzID0+IGNyZWRlbnRpYWxzID8gIGNyZWRlbnRpYWxzLnVzZXJuYW1lIDogbnVsbClcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgdG9rZW4oKTogT2JzZXJ2YWJsZSA8IHN0cmluZyB8IG51bGwgPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jcmVkZW50aWFscy5hc09ic2VydmFibGUoKS5waXBlKFxuICAgICAgICAgICAgbWFwKGNyZWRlbnRpYWxzID0+IGNyZWRlbnRpYWxzID8gIGNyZWRlbnRpYWxzLnRva2VuIDogbnVsbClcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGdW5jdGlvbnMgdGhhdCBvcGVucyBhIHdpbmRvdyBpbnN0ZWFkIG9mIGEgdGFiLlxuICAgICAqXG4gICAgICogU2VlIG1ldGhvZCBfZmlsdGVyTG9naW5PcHRpb25zIHJlZ2FyZGluZyBzZWN1cml0eSByaXNrcyBvZiBjZXJ0YWluXG4gICAgICogTG9naW5PcHRpb25zLlxuICAgICAqXG4gICAgICogQHBhcmFtIGxvZ2luT3B0aW9ucyBPcHRpb25zIHBhc3NlZCBhcyBVUkwgcGFyYW1ldGVycyB0byB0aGUgU1NPLlxuICAgICAqIEBwYXJhbSB3aWR0aCBQaXhlbCB3aWR0aCBvZiB0aGUgbG9naW4gd2luZG93LlxuICAgICAqIEBwYXJhbSBoZWlnaHQgUGl4ZWwgaGVpZ2h0IG9mIHRoZSBsb2dpbiB3aW5kb3cuXG4gICAgICogQHBhcmFtIHRvcCBQb3NpdGlvbiBvZiB0aGUgdG9wIGNvcm5lcnMuIElmIGl0IGlzIGEgbmVnYXRpdmVcbiAgICAgKiAgICAgICAgICAgICBudW1iZXIgaXQgY2VudHJlcyB0aGUgbG9naW4gd2luZG93IG9uIHRoZSBzY3JlZW4uXG4gICAgICogQHBhcmFtIGxlZnQgUG9zaXRpb24gb2YgdGhlIGxlZnQgY29ybmVycy4gSWYgaXQgaXMgYSBuZWdhdGl2ZVxuICAgICAqICAgICAgICAgICAgIG51bWJlciBpdCBjZW50cmVzIHRoZSBsb2dpbiB3aW5kb3cgb24gdGhlIHNjcmVlbi5cbiAgICAgKi9cbiAgICBwdWJsaWMgd2luZG93T3Blbihsb2dpbk9wdGlvbnM/OiBMb2dpbk9wdGlvbnMsIHdpZHRoID0gNjUwLCBoZWlnaHQgPSAxMDAwLCB0b3AgPSAtMSwgbGVmdCA9IC0xKSB7XG4gICAgICAgIGlmIChsZWZ0IDwgMCkge1xuICAgICAgICAgICAgY29uc3Qgc2NyZWVuV2lkdGggPSBzY3JlZW4ud2lkdGg7XG4gICAgICAgICAgICBpZiAoc2NyZWVuV2lkdGggPiB3aWR0aCkge1xuICAgICAgICAgICAgICAgIGxlZnQgPSBNYXRoLnJvdW5kKHNjcmVlbldpZHRoIC8gMiAtIHdpZHRoIC8gMik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRvcCA8IDApIHtcbiAgICAgICAgICAgIGNvbnN0IHNjcmVlbkhlaWdodCA9IHNjcmVlbi5oZWlnaHQ7XG4gICAgICAgICAgICBpZiAoc2NyZWVuSGVpZ2h0ID4gaGVpZ2h0KSB7XG4gICAgICAgICAgICAgICAgdG9wID0gTWF0aC5yb3VuZChzY3JlZW5IZWlnaHQgLyAyIC0gaGVpZ2h0IC8gMik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCB3aW5kb3dPcHRpb25zID0gW1xuICAgICAgICAgICAgYHdpZHRoPSR7d2lkdGh9YCxcbiAgICAgICAgICAgIGBoZWlnaHQ9JHtoZWlnaHR9YCxcbiAgICAgICAgICAgIGBsZWZ0PSR7bGVmdH1gLFxuICAgICAgICAgICAgYHRvcD0ke3RvcH1gLFxuICAgICAgICAgICAgJ3BlcnNvbmFsYmFyPW5vJyxcbiAgICAgICAgICAgICd0b29sYmFyPW5vJyxcbiAgICAgICAgICAgICdzY3JvbGxiYXJzPXllcycsXG4gICAgICAgICAgICAncmVzaXphYmxlPXllcycsXG4gICAgICAgICAgICAnZGlyZWN0b3JpZXM9bm8nLFxuICAgICAgICAgICAgJ2xvY2F0aW9uPW5vJyxcbiAgICAgICAgICAgICdtZW51YmFyPW5vJyxcbiAgICAgICAgICAgICd0aXRsZWJhcj1ubycsXG4gICAgICAgICAgICAndG9vbGJhcj1ubydcbiAgICAgICAgXTtcblxuICAgICAgICBjb25zdCBsb2dpbldpbmRvdyA9IHdpbmRvdy5vcGVuKHRoaXMuZ2V0U1NPVVJMKGxvZ2luT3B0aW9ucyksICdTaWduIGluIHRvIEVsaXhpcicsIHdpbmRvd09wdGlvbnMuam9pbignLCcpKTtcbiAgICAgICAgaWYgKGxvZ2luV2luZG93KSB7XG4gICAgICAgICAgICBsb2dpbldpbmRvdy5mb2N1cygpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRnVuY3Rpb25zIHRoYXQgb3BlbnMgYSB0YWIgKGluIG1vZGVybiBicm93c2VyKS5cbiAgICAgKlxuICAgICAqIFNlZSBtZXRob2QgX2ZpbHRlckxvZ2luT3B0aW9ucyByZWdhcmRpbmcgc2VjdXJpdHkgcmlza3Mgb2YgY2VydGFpblxuICAgICAqIExvZ2luT3B0aW9ucy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBsb2dpbk9wdGlvbnMgT3B0aW9ucyBwYXNzZWQgYXMgVVJMIHBhcmFtZXRlcnMgdG8gdGhlIFNTTy5cbiAgICAgKi9cbiAgICBwdWJsaWMgdGFiT3Blbihsb2dpbk9wdGlvbnM/OiBMb2dpbk9wdGlvbnMpIHtcbiAgICAgICAgY29uc3QgbG9naW5XaW5kb3cgPSB3aW5kb3cub3Blbih0aGlzLmdldFNTT1VSTChsb2dpbk9wdGlvbnMpLCAnU2lnbiBpbiB0byBFbGl4aXInKTtcbiAgICAgICAgaWYgKGxvZ2luV2luZG93KSB7XG4gICAgICAgICAgICBsb2dpbldpbmRvdy5mb2N1cygpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUHJvZHVjZXMgYSBVUkwgdGhhdCBhbGxvd3MgbG9nZ2luZyBpbnRvIHRoZSBzaW5nbGUgc2lnbiBvbiAoU1NPKSBwYWdlLlxuICAgICAqIFRoZSBVUkwgY2FucyBiZSBvcGVuZWQgaW4gYSBuZXcgdGFiIHVzaW5nIHRhcmdldD1cIl9ibGFua1wiLFxuICAgICAqIG9yIGluIGEgbmV3IHdpbmRvdyB1c2luZyB3aW5kb3cub3BlbigpLlxuICAgICAqXG4gICAgICogU2VlIG1ldGhvZCBfZmlsdGVyTG9naW5PcHRpb25zIHJlZ2FyZGluZyBzZWN1cml0eSByaXNrcyBvZiBjZXJ0YWluXG4gICAgICogTG9naW5PcHRpb25zLlxuICAgICAqXG4gICAgICogQHBhcmFtICBsb2dpbk9wdGlvbnMgT3B0aW9ucyBwYXNzZWQgYXMgVVJMIHBhcmFtZXRlcnMgdG8gdGhlIFNTTy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBTU08gVVJMLlxuICAgICAqXG4gICAgICovXG4gICAgICAgIHB1YmxpYyBnZXRTU09VUkwob3B0aW9ucz86IExvZ2luT3B0aW9ucyk6IHN0cmluZyB7XG4gICAgICAgIGxldCBleHRyYSA9ICcnO1xuICAgICAgICBpZiAob3B0aW9ucykge1xuICAgICAgICAgICAgdGhpcy5fZmlsdGVyTG9naW5PcHRpb25zKG9wdGlvbnMpO1xuICAgICAgICAgICAgZXh0cmEgPSBPYmplY3Qua2V5cyhvcHRpb25zKVxuICAgICAgICAgICAgICAgIC5tYXAoa2V5ID0+IFtrZXksIG9wdGlvbnNba2V5XV0pXG4gICAgICAgICAgICAgICAgLnJlZHVjZSgoYWNjdW11bGF0b3IsIGtleXZhbHVlKSA9PiBgJHthY2N1bXVsYXRvcn0mJHtrZXl2YWx1ZVswXX09JHtrZXl2YWx1ZVsxXX1gLCAnJyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGAke3RoaXMuX2FwcFVSTH0vc3NvP2Zyb209JHt0aGlzLl9kb21haW59JHtleHRyYX1gO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEZpbHRlcnMgb3B0aW9ucyB0aGF0IGFyZSB1bnNlY3VyZS5cbiAgICAgKlxuICAgICAqIFNlZSB0aGUgYWR2YW5jZSBvcHRpb25zIHRoYXQgY2FuIGJlIHJlcXVlc3RlZCB0aHJvdWdoIHRoZSBvcHRpb25zIHBhcmFtZXRlcjpcbiAgICAgKiBodHRwczovL2FwaS5hYWkuZWJpLmFjLnVrL2RvY3MvYXV0aGVudGljYXRpb24vYXV0aGVudGljYXRpb24uaW5kZXguaHRtbCNfY29tbW9uX2F0dHJpYnV0ZXNcbiAgICAgKlxuICAgICAqIFRoZSB0aW1lIHRvIGxpdmUgcGFyYW1lbnRlciAodHRsKSBkZWZhdWx0IHZhbHVlIGlzIDYwIG1pbnV0ZXMuIEl0IGlzIGFcbiAgICAgKiBiaWcgc2VjdXJpdHkgcmlzayB0byByZXF1ZXN0IGxvbmdlciB0dGwuIElmIGEgdGhpcmQgcGFydHkgZ2V0cyBob2xkIG9mXG4gICAgICogc3VjaCB0b2tlbiwgbWVhbnMgdGhhdCB0aGV5IGNvdWxkIHVzZSBpdCBmb3IgYSBkYXksIHdlZWssIHllYXJcbiAgICAgKiAoZXNzZW50aWFsbHksIGxpa2UgaGF2aW5nIHRoZSB1c2VybmFtZS9wYXNzd29yZCkuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gIGxvZ2luT3B0aW9ucyBPcHRpb25zIHBhc3NlZCBhcyBVUkwgcGFyYW1ldGVycyB0byB0aGUgU1NPLlxuICAgICAqXG4gICAgICpcbiAgICAgKi9cbiAgICBwdWJsaWMgX2ZpbHRlckxvZ2luT3B0aW9ucyhvcHRpb25zOiBMb2dpbk9wdGlvbnMpIHtcbiAgICAgICAgaWYgKE9iamVjdC5rZXlzKG9wdGlvbnMpLmluZGV4T2YoJ3R0bCcpID4gLTEpIHtcbiAgICAgICAgICAgIGNvbnN0IHR0bDogbnVtYmVyID0gK29wdGlvbnNbJ3R0bCddO1xuICAgICAgICAgICAgY29uc3Qgc29mdExpbWl0ID0gNjA7XG4gICAgICAgICAgICBjb25zdCBoYXJkTGltaXQgPSA2MCAqIDI0O1xuICAgICAgICAgICAgaWYgKHR0bCA+IGhhcmRMaW1pdCkge1xuICAgICAgICAgICAgICAgIHdpbmRvdy5jb25zb2xlLmVycm9yKGBMb2dpbiByZXF1ZXN0ZWQgd2l0aCBhbiBleHBpcmF0aW9uIGxvbmdlciB0aGFuICR7aGFyZExpbWl0fSBtaW51dGVzISBUaGlzIGlzIG5vdCBhbGxvd2VkLmApO1xuICAgICAgICAgICAgICAgIHdpbmRvdy5jb25zb2xlLmVycm9yKGBFeHBpcmF0aW9uIHJlcXVlc3QgcmVzZXQgdG8gJHtoYXJkTGltaXR9IG1pbnV0ZXMuYCk7XG4gICAgICAgICAgICAgICAgb3B0aW9uc1sndHRsJ10gPSAnJyArIGhhcmRMaW1pdDtcbiAgICAgICAgICAgIH0gZWxzZSBpZiAodHRsID4gc29mdExpbWl0KSB7XG4gICAgICAgICAgICAgICAgd2luZG93LmNvbnNvbGUud2FybihgTG9naW4gcmVxdWVzdGVkIHdpdGggYW4gZXhwaXJhdGlvbiBsb25nZXIgdGhhbiAke3NvZnRMaW1pdH0gbWludXRlcyFgKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEZ1bmN0aW9ucyB0aGF0IGxvZ3Mgb3V0IHRoZSB1c2VyLlxuICAgICAqIEl0IHRyaWdnZXJzIHRoZSBsb2dvdXQgY2FsbGJhY2tzLlxuICAgICAqIEl0IGlzIGFuIGFycm93IGZ1bmN0aW9uIChsYW1iZGEpIGJlY2F1c2UgaW4gdGhhdCB3YXkgaXQgaGFzIGEgcmVmZXJlbmNlXG4gICAgICogdG8gJ3RoaXMnIHdoZW4gdXNlZCBpbiBzZXRUaW1lb3V0IGNhbGwuXG4gICAgICovXG4gICAgcHVibGljIGxvZ091dCgpIHtcbiAgICAgICAgdGhpcy5fc3RvcmFnZVJlbW92ZXIoKTtcbiAgICAgICAgdGhpcy5fdXBkYXRlQ3JlZGVudGlhbHMoKTtcblxuICAgICAgICAvLyBUcmlnZ2VycyB1cGRhdGluZyBvdGhlciB3aW5kb3dzXG4gICAgICAgIHRoaXMuX2NvbW1LZXlVcGRhdGVyKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQWRkIGEgY2FsbGJhY2sgdG8gdGhlIExvZ0luIGV2ZW50LlxuICAgICAqXG4gICAgICogQHBhcmFtIGNhbGxiYWNrIFRoZSBGdW5jdGlvbiBjYWxsZWQgd2hlbiB0aGUgbG9naW4gZXZlbnQgaXMgdHJpZ2dlcmVkIGFuZCB0aGVcbiAgICAgKiAgICBKV1QgdG9rZW4gaXMgcmVjZWl2ZWQgYW5kIGFjY2VwdGVkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIGV2ZW50IHJlZ2lzdHJhdGlvbiBpZCAobmVjZXNzYXJ5IHRvIHVucmVnaXN0ZXIgdGhlIGV2ZW50KS5cbiAgICAgKi9cbiAgICBwdWJsaWMgYWRkTG9nSW5FdmVudExpc3RlbmVyKGNhbGxiYWNrOiBGdW5jdGlvbik6IG51bWJlciB7XG4gICAgICAgIHJldHVybiB0aGlzLl9sb2dpbkNhbGxiYWNrcy5wdXNoKGNhbGxiYWNrKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZW1vdmUgYSBjYWxsYmFjayBmcm9tIHRoZSBMb2dJbiBldmVudC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBpZCBUaGUgaWQgZ2l2ZW4gd2hlbiBldmVudCBsaXN0ZW5lciB3YXMgYWRkZWQuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyB0cnVlIHdoZW4gcmVtb3ZlIHN1Y2Nlc3NmdWxseSwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgICAqL1xuICAgIHB1YmxpYyByZW1vdmVMb2dJbkV2ZW50TGlzdGVuZXIoaWQ6IG51bWJlcik6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gZGVsZXRlIHRoaXMuX2xvZ2luQ2FsbGJhY2tzW2lkIC0gMV07XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQWRkIGEgY2FsbGJhY2sgdG8gdGhlIExvZ091dCBldmVudC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBjYWxsYmFjayBUaGUgRnVuY3Rpb24gY2FsbGVkIHdoZW4gdGhlIGxvZ291dCBldmVudCBpcyB0cmlnZ2VyZWQgYW5kIHRoZVxuICAgICAqICAgIEpXVCB0b2tlbiBpcyByZWNlaXZlZCBhbmQgYWNjZXB0ZWQuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyBUaGUgcmVnaXN0cmF0aW9uIGlkIChuZWNlc3NhcnkgdG8gdW5yZWdpc3RlciB0aGUgZXZlbnQpLlxuICAgICAqL1xuICAgIHB1YmxpYyBhZGRMb2dPdXRFdmVudExpc3RlbmVyKGNhbGxiYWNrOiBGdW5jdGlvbik6IG51bWJlciB7XG4gICAgICAgIHJldHVybiB0aGlzLl9sb2dvdXRDYWxsYmFja3MucHVzaChjYWxsYmFjayk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlIGEgY2FsbGJhY2sgZnJvbSB0aGUgTG9nT3V0IGV2ZW50LlxuICAgICAqXG4gICAgICogQHBhcmFtIGlkIFRoZSBpZCBnaXZlbiB3aGVuIGV2ZW50IGxpc3RlbmVyIHdhcyBhZGRlZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIHRydWUgd2hlbiByZW1vdmUgc3VjY2Vzc2Z1bGx5LCBmYWxzZSBvdGhlcndpc2UuXG4gICAgICovXG4gICAgcHVibGljIHJlbW92ZUxvZ091dEV2ZW50TGlzdGVuZXIoaWQ6IG51bWJlcik6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gZGVsZXRlIHRoaXMuX2xvZ291dENhbGxiYWNrc1tpZCAtIDFdO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIExpc3RlbiBmb3IgbG9naW4gbWVzc2FnZXMgZnJvbSBvdGhlciB3aW5kb3dzLlxuICAgICAqIFRoZXNlIG1lc3NhZ2VzIGNvbnRhaW4gdGhlIHRva2VucyBmcm9tIHRoZSBBQVAuXG4gICAgICogSWYgYSB0b2tlbiBpcyByZWNlaXZlZCB0aGVuIHRoZSBjYWxsYmFja3MgYXJlIHRyaWdnZXJlZC5cbiAgICAgKi9cbiAgICBwcml2YXRlIF9saXN0ZW5Mb2dpbk1lc3NhZ2UocmVuZGVyZXI6IFJlbmRlcmVyMikge1xuICAgICAgICByZW5kZXJlci5saXN0ZW4oJ3dpbmRvdycsICdtZXNzYWdlJywgKGV2ZW50OiBNZXNzYWdlRXZlbnQpID0+IHtcbiAgICAgICAgICAgIGlmICghdGhpcy5tZXNzYWdlSXNBY2NlcHRhYmxlKGV2ZW50KSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHRoaXMuX3N0b3JhZ2VVcGRhdGVyKGV2ZW50LmRhdGEpO1xuICAgICAgICAgICAgZXZlbnQuc291cmNlLmNsb3NlKCk7XG4gICAgICAgICAgICB0aGlzLl91cGRhdGVDcmVkZW50aWFscygpO1xuXG4gICAgICAgICAgICAvLyBUcmlnZ2VycyB1cGRhdGluZyBvdGhlciB3aW5kb3dzXG4gICAgICAgICAgICB0aGlzLl9jb21tS2V5VXBkYXRlcigpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKiogTGlzdGVuIHRvIGNoYW5nZXMgaW4gdGhlIHRva2VuIGZyb20gKm90aGVyKiB3aW5kb3dzLlxuICAgICAqXG4gICAgICogRm9yIGludGVyLXdpbmRvdyBjb21tdW5pY2F0aW9uIG1lc3NhZ2VzIGFyZSB0cmFuc21pdHRlZCB0cm91Z2ggY2hhbmdlc1xuICAgICAqIG9uIGEgZHVtbXkgc3RvcmFnZSBrZXkgcHJvcGVydHk6ICdfY29tbUtleU5hbWUnLlxuICAgICAqXG4gICAgICogTm90aWNlIHRoYXQgY2hhbmdlcyBpbiB0aGUgJ19jb21tS2V5TmFtZScgcHJvZHVjZWQgYnkgdGhpcyBjbGFzcyBkb2Vzbid0XG4gICAgICogdHJpZ2dlciB0aGlzIGV2ZW50LlxuICAgICAqL1xuICAgIHByaXZhdGUgX2xpc3RlbkNoYW5nZXNGcm9tT3RoZXJXaW5kb3dzKHJlbmRlcmVyOiBSZW5kZXJlcjIpIHtcbiAgICAgICAgcmVuZGVyZXIubGlzdGVuKCd3aW5kb3cnLCAnc3RvcmFnZScsIChldmVudDogU3RvcmFnZUV2ZW50KSA9PiB7XG4gICAgICAgICAgICBpZiAoZXZlbnQua2V5ID09PSB0aGlzLl9jb21tS2V5TmFtZSkge1xuICAgICAgICAgICAgICAgIHRoaXMuX3VwZGF0ZUNyZWRlbnRpYWxzKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENoZWNrIGlmIHRoZSBtZXNzYWdlIGlzIGNvbWluZyBmcm9tIHRoZSBzYW1lIGRvbWFpbiB3ZSB1c2UgdG8gZ2VuZXJhdGVcbiAgICAgKiB0aGUgU1NPIFVSTCwgb3RoZXJ3aXNlIGl0J3MgaWZmeSBhbmQgc2hvdWxkbid0IHRydXN0IGl0LlxuICAgICAqL1xuICAgIHByaXZhdGUgbWVzc2FnZUlzQWNjZXB0YWJsZShldmVudDogTWVzc2FnZUV2ZW50KTogYm9vbGVhbiB7XG4gICAgICAgIHJldHVybiBldmVudC5vcmlnaW4gPT09IHRoaXMuX2FwcFVSTDtcbiAgICB9XG5cbiAgICBwcml2YXRlIF91cGRhdGVDcmVkZW50aWFscygpIHtcbiAgICAgICAgY29uc3QgaXNBdXRoZW50aWNhdGVkID0gdGhpcy5fbG9nZ2VkSW4oKTtcblxuICAgICAgICBpZiAodGhpcy5fdGltZW91dElEKSB7XG4gICAgICAgICAgICB3aW5kb3cuY2xlYXJUaW1lb3V0KHRoaXMuX3RpbWVvdXRJRCk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNBdXRoZW50aWNhdGVkKSB7XG4gICAgICAgICAgICB0aGlzLl9jcmVkZW50aWFscy5uZXh0KHtcbiAgICAgICAgICAgICAgICByZWFsbmFtZTogPCBzdHJpbmcgPiB0aGlzLl9nZXRSZWFsTmFtZSgpLFxuICAgICAgICAgICAgICAgIHVzZXJuYW1lOiA8IHN0cmluZyA+IHRoaXMuX2dldFVzZXJOYW1lKCksXG4gICAgICAgICAgICAgICAgdG9rZW46IDwgc3RyaW5nID4gdGhpcy5fZ2V0VG9rZW4oKVxuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIHRoaXMuX2xvZ2luQ2FsbGJhY2tzLm1hcChjYWxsYmFjayA9PiBjYWxsYmFjayAmJiBjYWxsYmFjaygpKTtcblxuICAgICAgICAgICAgLy8gU2NoZWR1bGUgZnV0dXJlIGxvZ291dCBldmVudCBiYXNlIG9uIHRva2VuIGV4cGlyYXRpb25cbiAgICAgICAgICAgIGNvbnN0IGV4cGlyZURhdGUgPSA8IERhdGUgPiB0aGlzLl90b2tlblNlcnZpY2UuZ2V0VG9rZW5FeHBpcmF0aW9uRGF0ZSgpO1xuICAgICAgICAgICAgLy8gQ29lcmNpbmcgZGF0ZXMgdG8gbnVtYmVycyB3aXRoIHRoZSB1bmFyeSBvcGVyYXRvciAnKydcbiAgICAgICAgICAgIGNvbnN0IGRlbGF5ID0gK2V4cGlyZURhdGUgLSArbmV3IERhdGUoKTtcbiAgICAgICAgICAgIHRoaXMuX3RpbWVvdXRJRCA9IHdpbmRvdy5zZXRUaW1lb3V0KCgpID0+IHRoaXMubG9nT3V0KCksIGRlbGF5KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuX3N0b3JhZ2VSZW1vdmVyKCk7IC8vIENsZWFudXAgcG9zc2libGUgbGVmdCBiZWhpbmQgdG9rZW5cbiAgICAgICAgICAgIHRoaXMuX2NyZWRlbnRpYWxzLm5leHQobnVsbCk7XG4gICAgICAgICAgICB0aGlzLl9sb2dvdXRDYWxsYmFja3MubWFwKGNhbGxiYWNrID0+IGNhbGxiYWNrICYmIGNhbGxiYWNrKCkpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ2hlY2sgaWYgdGhlcmUncyBhIHVzZXIgbG9nZ2luZyBvbiBhbmQgd2hldGhlciB0aGUgdG9rZW4gaXMgc3RpbGwgdmFsaWQuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyAgV2hldGhlciB0aGUgdXNlciB1c2VyIGlzIGF1dGhlbnRpY2F0ZWQgb3Igbm90LlxuICAgICAqL1xuICAgIHByaXZhdGUgX2xvZ2dlZEluKCk6IGJvb2xlYW4ge1xuICAgICAgICByZXR1cm4gdGhpcy5fdG9rZW5TZXJ2aWNlLmlzVG9rZW5WYWxpZCgpO1xuICAgIH1cblxuICAgIHByaXZhdGUgX2dldFRva2VuKCk6IHN0cmluZyB8IG51bGwge1xuICAgICAgICByZXR1cm4gdGhpcy5fdG9rZW5TZXJ2aWNlLmdldFRva2VuKCk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBfZ2V0VXNlck5hbWUoKTogc3RyaW5nIHwgbnVsbCB7XG4gICAgICAgIHJldHVybiB0aGlzLl90b2tlblNlcnZpY2UuZ2V0Q2xhaW0gPCBzdHJpbmcsIG51bGwgPiAoJ2VtYWlsJywgbnVsbCk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBfZ2V0UmVhbE5hbWUoKTogc3RyaW5nIHwgbnVsbCB7XG4gICAgICAgIHJldHVybiB0aGlzLl90b2tlblNlcnZpY2UuZ2V0Q2xhaW0gPCBzdHJpbmcsIG51bGwgPiAoJ25hbWUnLCBudWxsKTtcbiAgICB9XG5cbn1cbiJdfQ==