UNPKG

ng-ebi-authorization

Version:

The ng-ebi-authorization is a simple authentication Angular library that relies on EBI's Authentication and Authorization Profile (AAP) infrastructure. After successful login, a JWT token is stored on the browser (via cookie, local or session storage).

601 lines 53.9 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ import { Injectable, Inject, RendererFactory2 } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { BehaviorSubject, } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { AAP_CONFIG } from './auth.config'; import { TokenService } from './token.service'; /** * @record */ export function LoginOptions() { } /** * @record */ export function User() { } if (false) { /** @type {?} */ User.prototype.uid; /** @type {?} */ User.prototype.name; /** @type {?} */ User.prototype.nickname; /** @type {?} */ User.prototype.email; /** @type {?} */ User.prototype.token; } export class AuthService { /** * @param {?} _rendererFactory * @param {?} _tokenService * @param {?} _http * @param {?} config */ constructor(_rendererFactory, _tokenService, _http, config) { this._rendererFactory = _rendererFactory; this._tokenService = _tokenService; this._http = _http; this.config = config; this._user = new BehaviorSubject(null); this._currentState = null; // stores the string token or null otherwise (logout) // stores the string token or null otherwise (logout) this._loginCallbacks = []; this._logoutCallbacks = []; this._timeoutID = null; // This two properties are used for inter-window communication. // It is achieve through the update of the dummy key storage '_commKeyName' this._commKeyName = 'AngularAapAuthUpdated'; this._commKeyUpdater = () => localStorage.setItem(this._commKeyName, '' + new Date().getTime()); this._domain = encodeURIComponent(window.location.origin); this._appURL = config.aapURL.replace(/\/$/, ''); this._authURL = `${this._appURL}/auth`; this._tokenURL = `${this._appURL}/token`; this._storageUpdater = config.tokenUpdater; if (config.tokenRemover) { this._storageRemover = config.tokenRemover; } else { this._storageRemover = () => config.tokenUpdater(null); } /** @type {?} */ const renderer = this._rendererFactory.createRenderer(null, null); this._unlistenEvents = [ this._listenLoginMessage(renderer), this._listenChangesFromOtherWindows(renderer) ]; this._currentState = null; this._updateUser(); // TODO: experiment with setTimeOut } /** * @return {?} */ ngOnDestroy() { this._unlistenEvents.forEach(fn => fn()); } /** * @return {?} */ user() { return this._user.asObservable(); } /** * Functions that opens a window instead of a tab. * * See method _filterLoginOptions regarding security risks of certain * LoginOptions. * * @param {?=} options * @param {?=} width Pixel width of the login window. * @param {?=} height Pixel height of the login window. * @param {?=} left Position of the left corners. If it is a negative * number it centres the login window on the screen. * @param {?=} top Position of the top corners. If it is a negative * number it centres the login window on the screen. * @return {?} */ openLoginWindow(options, width = 650, height = 1000, left = -1, top = -1) { if (left < 0) { /** @type {?} */ const screenWidth = screen.width; if (screenWidth > width) { left = Math.round((screenWidth - width) / 2); } } if (top < 0) { /** @type {?} */ const screenHeight = screen.height; if (screenHeight > height) { top = Math.round((screenHeight - height) / 2); } } /** @type {?} */ const 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' ]; /** @type {?} */ const loginWindow = window.open(this.getSSOURL(options), 'Sign in to Elixir', windowOptions.join(',')); if (loginWindow) { loginWindow.focus(); } } /** * @deprecated use openLoginWindow method instead (top and left arguments are inverted in the new method) * since version 1.0.0-beta.5. * windowOpen will be deleted in version 1.0.0 * @param {?=} options * @param {?=} width * @param {?=} height * @param {?=} top * @param {?=} left * @return {?} */ windowOpen(options, width = 650, height = 1000, top = -1, left = -1) { this._deprecationWarning('windowOpen', 'openLoginWindow'); this.openLoginWindow(options, width, height, left, top); } /** * Functions that opens a tab (in modern browser). * * See method _filterLoginOptions regarding security risks of certain * LoginOptions. * * @param {?=} options * @return {?} */ openLoginTab(options) { /** @type {?} */ const loginWindow = window.open(this.getSSOURL(options), 'Sign in to Elixir'); if (loginWindow) { loginWindow.focus(); } } /** * @deprecated use openLoginTab method instead * since version 1.0.0-beta.5. * tabOpen will be deleted in version 1.0.0 * @param {?=} options * @return {?} */ tabOpen(options) { this._deprecationWarning('tabOpen', 'openLoginTab'); this.openLoginTab(options); } /** * 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) { /** @type {?} */ const fragments = this._formatFragments(Object.assign({ 'from': this._domain }, options)); return `${this._appURL}/sso${fragments}`; } /** * 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._updateUser(); // Triggers updating other windows this._commKeyUpdater(); } /** * Create AAP account * * @param {?} newUser * @return {?} uid of the new user */ createAAPaccount(newUser) { return this._http.post(this._authURL, newUser, { responseType: 'text' }); } /** * Login directly through the AAP * * See method _filterLoginOptions regarding security risks of certain * LoginOptions. * * @param {?} user * @param {?=} options * @return {?} true if the user successfully login, otherwise false */ loginAAP(user, options) { /** @type {?} */ const fragments = this._formatFragments(options); return this._http.get(`${this._authURL}${fragments}`, { headers: this._createAuthHeader(user), responseType: 'text' }).pipe(tap(token => { this._storageRemover(); this._storageUpdater(token); this._updateUser(); // Triggers updating other windows this._commKeyUpdater(); }), map(Boolean)); } /** * Change password AAP account * * @param {?} __0 * @return {?} true when password is successfully changed */ changePasswordAAP({ username, oldPassword, newPassword }) { return this._http.patch(this._authURL, { username, password: newPassword }, { headers: this._createAuthHeader({ username, password: oldPassword }) }).pipe(map(response => true) // response is empty, but if it reaches this point the request has successfully completed ); } /** * 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]; } /** * Refresh token * * @return {?} true when token is successfully refreshed */ refresh() { return this._http.get(this._tokenURL, { responseType: 'text' }).pipe(tap(token => { this._storageRemover(); this._storageUpdater(token); this._updateUser(); // Triggers updating other windows this._commKeyUpdater(); }), map(Boolean)); } /** * Format and filter fragment options * * \@params options * * @private * @param {?=} options * @return {?} fragment string */ _formatFragments(options) { if (options) { this._filterLoginOptions(options); return '?' + Object.entries(options) .map(([key, value]) => `${key}=${value}`).join('&'); } return ''; } /** * 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). * * @private * @param {?} options * @return {?} */ _filterLoginOptions(options) { if (Object.keys(options).indexOf('ttl') > -1) { /** @type {?} */ const ttl = +options['ttl']; /** @type {?} */ const softLimit = 60; /** @type {?} */ const hardLimit = 60 * 24; if (ttl > hardLimit) { throw (new Error(`Login requested with an expiration longer than ${hardLimit} minutes! This is not allowed.`)); } if (ttl > softLimit) { window.console.warn(`Login requested with an expiration longer than ${softLimit} minutes!`); } } } /** * Creates Authorization header * * @private * @param {?} __0 * @return {?} New authorization header */ _createAuthHeader({ username, password }) { /** @type {?} */ const authToken = btoa(`${username}:${password}`); return new HttpHeaders({ 'Authorization': `Basic ${authToken}` }); } /** * 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. * @private * @param {?} renderer * @return {?} */ _listenLoginMessage(renderer) { return renderer.listen('window', 'message', (event) => { if (!this._messageIsAcceptable(event)) { return; } this._storageUpdater(event.data); // I don't know how to type guard event.source // This doesn't work // if (event.source instanceof Window) { if (event.source) { ((/** @type {?} */ (event.source))).close(); } this._updateUser(); // 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. * @private * @param {?} renderer * @return {?} */ _listenChangesFromOtherWindows(renderer) { return renderer.listen('window', 'storage', (event) => { if (event.key === this._commKeyName) { this._updateUser(); } }); } /** * 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. * @private * @param {?} event * @return {?} */ _messageIsAcceptable(event) { return event.origin === this._appURL; } /** * @private * @return {?} */ _updateUser() { if (this._timeoutID) { window.clearTimeout(this._timeoutID); } if (this._tokenService.isTokenValid()) { /** @type {?} */ const token = (/** @type {?} */ (this._tokenService.getToken())); this._user.next({ uid: this._getClaim('sub'), name: this._getClaim('name'), nickname: this._getClaim('nickname'), email: this._getClaim('email'), token }); if (this._currentState === null) { // this._loginCallbacks is an empty list when first called from the constructor. // Latter it could be filled as clients of the library call `this.addLogInEventListener(myfunction)` this._loginCallbacks.forEach(callback => callback()); } // Schedule future logout event base on token expiration /** @type {?} */ const expireDate = (/** @type {?} */ (this._tokenService.getTokenExpirationDate())); // Coercing dates to numbers with the unary operator '+' /** @type {?} */ const delay = +expireDate - +new Date(); this._timeoutID = window.setTimeout(() => this.logOut(), delay); if (this._currentState !== token) { this._currentState = token; } } else { this._storageRemover(); // Cleanup possible left behind token if (this._currentState !== null) { // this._logoutCallbacks is an empty list when first called from the constructor. // Latter it could be filled as clients of the library call `this.addLogOutEventListener(myfunction)` this._logoutCallbacks.forEach(callback => callback()); this._user.next(null); } this._currentState = null; } } /** * @private * @param {?} claim * @return {?} */ _getClaim(claim) { return this._tokenService.getClaim(claim, ''); } /** * @private * @param {?} oldMethod * @param {?} newMethod * @return {?} */ _deprecationWarning(oldMethod, newMethod) { window.console.warn(`Method '${oldMethod}' has been deprecated, please use '${newMethod}' method instead`); } } AuthService.decorators = [ { type: Injectable } ]; /** @nocollapse */ AuthService.ctorParameters = () => [ { type: RendererFactory2 }, { type: TokenService }, { type: HttpClient }, { type: undefined, decorators: [{ type: Inject, args: [AAP_CONFIG,] }] } ]; if (false) { /** * @type {?} * @private */ AuthService.prototype._user; /** * @type {?} * @private */ AuthService.prototype._currentState; /** * @type {?} * @private */ AuthService.prototype._loginCallbacks; /** * @type {?} * @private */ AuthService.prototype._logoutCallbacks; /** * @type {?} * @private */ AuthService.prototype._unlistenEvents; /** * @type {?} * @private */ AuthService.prototype._timeoutID; /** * @type {?} * @private */ AuthService.prototype._domain; /** * @type {?} * @private */ AuthService.prototype._appURL; /** * @type {?} * @private */ AuthService.prototype._tokenURL; /** * @type {?} * @private */ AuthService.prototype._authURL; /** * @type {?} * @private */ AuthService.prototype._storageUpdater; /** * @type {?} * @private */ AuthService.prototype._storageRemover; /** * @type {?} * @private */ AuthService.prototype._commKeyName; /** * @type {?} * @private */ AuthService.prototype._commKeyUpdater; /** * @type {?} * @private */ AuthService.prototype._rendererFactory; /** * @type {?} * @private */ AuthService.prototype._tokenService; /** * @type {?} * @private */ AuthService.prototype._http; /** * @type {?} * @private */ AuthService.prototype.config; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6Im5nOi8vbmctZWJpLWF1dGhvcml6YXRpb24vIiwic291cmNlcyI6WyJhdXRoL2F1dGguc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsT0FBTyxFQUNILFVBQVUsRUFDVixNQUFNLEVBRU4sZ0JBQWdCLEVBRW5CLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFDSCxVQUFVLEVBQ1YsV0FBVyxFQUVkLE1BQU0sc0JBQXNCLENBQUM7QUFJOUIsT0FBTyxFQUNILGVBQWUsR0FDbEIsTUFBTSxNQUFNLENBQUM7QUFDZCxPQUFPLEVBRUgsR0FBRyxFQUNILEdBQUcsRUFDTixNQUFNLGdCQUFnQixDQUFDO0FBRXhCLE9BQU8sRUFDSCxVQUFVLEVBRWIsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUNILFlBQVksRUFDZixNQUFNLGlCQUFpQixDQUFDOzs7O0FBRXpCLGtDQUVDOzs7O0FBRUQsMEJBTUM7OztJQUxHLG1CQUFZOztJQUNaLG9CQUFhOztJQUNiLHdCQUFpQjs7SUFDakIscUJBQWM7O0lBQ2QscUJBQWM7O0FBSWxCLE1BQU0sT0FBTyxXQUFXOzs7Ozs7O0lBeUJwQixZQUNZLGdCQUFrQyxFQUNsQyxhQUEyQixFQUMzQixLQUFpQixFQUNHLE1BQWtCO1FBSHRDLHFCQUFnQixHQUFoQixnQkFBZ0IsQ0FBa0I7UUFDbEMsa0JBQWEsR0FBYixhQUFhLENBQWM7UUFDM0IsVUFBSyxHQUFMLEtBQUssQ0FBWTtRQUNHLFdBQU0sR0FBTixNQUFNLENBQVk7UUEzQjFDLFVBQUssR0FBRyxJQUFJLGVBQWUsQ0FBa0IsSUFBSSxDQUFDLENBQUM7UUFDbkQsa0JBQWEsR0FBa0IsSUFBSSxDQUFDLENBQUMscURBQXFEOztRQUUxRixvQkFBZSxHQUFlLEVBQUUsQ0FBQztRQUNqQyxxQkFBZ0IsR0FBZSxFQUFFLENBQUM7UUFJbEMsZUFBVSxHQUFrQixJQUFJLENBQUM7OztRQVl4Qixp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Q25DLDRCQUF5Qjs7Ozs7SUFDekIsNkJBQThDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgICBJbmplY3RhYmxlLFxuICAgIEluamVjdCxcbiAgICBPbkRlc3Ryb3ksXG4gICAgUmVuZGVyZXJGYWN0b3J5MixcbiAgICBSZW5kZXJlcjJcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQge1xuICAgIEh0dHBDbGllbnQsXG4gICAgSHR0cEhlYWRlcnMsXG4gICAgSHR0cFJlc3BvbnNlXG59IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbi9odHRwJztcbmltcG9ydCB7XG4gICAgT2JzZXJ2YWJsZVxufSBmcm9tICdyeGpzJztcbmltcG9ydCB7XG4gICAgQmVoYXZpb3JTdWJqZWN0LFxufSBmcm9tICdyeGpzJztcbmltcG9ydCB7XG4gICAgZmlsdGVyLFxuICAgIG1hcCxcbiAgICB0YXBcbn0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuXG5pbXBvcnQge1xuICAgIEFBUF9DT05GSUcsXG4gICAgQXV0aENvbmZpZ1xufSBmcm9tICcuL2F1dGguY29uZmlnJztcbmltcG9ydCB7XG4gICAgVG9rZW5TZXJ2aWNlXG59IGZyb20gJy4vdG9rZW4uc2VydmljZSc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgTG9naW5PcHRpb25zIHtcbiAgICBba2V5OiBzdHJpbmddOiBzdHJpbmc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgVXNlciB7XG4gICAgdWlkOiBzdHJpbmc7XG4gICAgbmFtZTogc3RyaW5nO1xuICAgIG5pY2tuYW1lOiBzdHJpbmc7XG4gICAgZW1haWw6IHN0cmluZztcbiAgICB0b2tlbjogc3RyaW5nO1xufVxuXG5ASW5qZWN0YWJsZSgpXG5leHBvcnQgY2xhc3MgQXV0aFNlcnZpY2UgaW1wbGVtZW50cyBPbkRlc3Ryb3kge1xuXG4gICAgcHJpdmF0ZSBfdXNlciA9IG5ldyBCZWhhdmlvclN1YmplY3QgPCBVc2VyIHwgbnVsbCA+IChudWxsKTtcbiAgICBwcml2YXRlIF9jdXJyZW50U3RhdGU6IHN0cmluZyB8IG51bGwgPSBudWxsOyAvLyBzdG9yZXMgdGhlIHN0cmluZyB0b2tlbiBvciBudWxsIG90aGVyd2lzZSAobG9nb3V0KVxuXG4gICAgcHJpdmF0ZSBfbG9naW5DYWxsYmFja3M6IEZ1bmN0aW9uW10gPSBbXTtcbiAgICBwcml2YXRlIF9sb2dvdXRDYWxsYmFja3M6IEZ1bmN0aW9uW10gPSBbXTtcblxuICAgIHByaXZhdGUgX3VubGlzdGVuRXZlbnRzOiBGdW5jdGlvbltdO1xuXG4gICAgcHJpdmF0ZSBfdGltZW91dElEOiBudW1iZXIgfCBudWxsID0gbnVsbDtcblxuICAgIC8vIENvbmZpZ3VyYXRpb25cbiAgICBwcml2YXRlIHJlYWRvbmx5IF9kb21haW46IHN0cmluZztcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9hcHBVUkw6IHN0cmluZztcbiAgICBwcml2YXRlIHJlYWRvbmx5IF90b2tlblVSTDogc3RyaW5nO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX2F1dGhVUkw6IHN0cmluZztcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9zdG9yYWdlVXBkYXRlcjogKG5ld1Rva2VuOiBhbnkpID0+IHZvaWQ7XG4gICAgcHJpdmF0ZSByZWFkb25seSBfc3RvcmFnZVJlbW92ZXI6ICgpID0+IHZvaWQ7XG5cbiAgICAvLyBUaGlzIHR3byBwcm9wZXJ0aWVzIGFyZSB1c2VkIGZvciBpbnRlci13aW5kb3cgY29tbXVuaWNhdGlvbi5cbiAgICAvLyBJdCBpcyBhY2hpZXZlIHRocm91Z2ggdGhlIHVwZGF0ZSBvZiB0aGUgZHVtbXkga2V5IHN0b3JhZ2UgJ19jb21tS2V5TmFtZSdcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9jb21tS2V5TmFtZSA9ICdBbmd1bGFyQWFwQXV0aFVwZGF0ZWQnO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX2NvbW1LZXlVcGRhdGVyID0gKCkgPT4gbG9jYWxTdG9yYWdlLnNldEl0ZW0odGhpcy5fY29tbUtleU5hbWUsICcnICsgbmV3IERhdGUoKS5nZXRUaW1lKCkpO1xuXG4gICAgY29uc3RydWN0b3IoXG4gICAgICAgIHByaXZhdGUgX3JlbmRlcmVyRmFjdG9yeTogUmVuZGVyZXJGYWN0b3J5MixcbiAgICAgICAgcHJpdmF0ZSBfdG9rZW5TZXJ2aWNlOiBUb2tlblNlcnZpY2UsXG4gICAgICAgIHByaXZhdGUgX2h0dHA6IEh0dHBDbGllbnQsXG4gICAgICAgIEBJbmplY3QoQUFQX0NPTkZJRykgcHJpdmF0ZSBjb25maWc6IEF1dGhDb25maWdcbiAgICApIHtcbiAgICAgICAgdGhpcy5fZG9tYWluID0gZW5jb2RlVVJJQ29tcG9uZW50KHdpbmRvdy5sb2NhdGlvbi5vcmlnaW4pO1xuXG4gICAgICAgIHRoaXMuX2FwcFVSTCA9IGNvbmZpZy5hYXBVUkwucmVwbGFjZSgvXFwvJC8sICcnKTtcbiAgICAgICAgdGhpcy5fYXV0aFVSTCA9IGAke3RoaXMuX2FwcFVSTH0vYXV0aGA7XG4gICAgICAgIHRoaXMuX3Rva2VuVVJMID0gYCR7dGhpcy5fYXBwVVJMfS90b2tlbmA7XG5cbiAgICAgICAgdGhpcy5fc3RvcmFnZVVwZGF0ZXIgPSBjb25maWcudG9rZW5VcGRhdGVyO1xuICAgICAgICBpZiAoY29uZmlnLnRva2VuUmVtb3Zlcikge1xuICAgICAgICAgICAgdGhpcy5fc3RvcmFnZVJlbW92ZXIgPSBjb25maWcudG9rZW5SZW1vdmVyO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5fc3RvcmFnZVJlbW92ZXIgPSAoKSA9PiBjb25maWcudG9rZW5VcGRhdGVyKG51bGwpO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgcmVuZGVyZXIgPSB0aGlzLl9yZW5kZXJlckZhY3RvcnkuY3JlYXRlUmVuZGVyZXIobnVsbCwgbnVsbCk7XG4gICAgICAgIHRoaXMuX3VubGlzdGVuRXZlbnRzID0gW1xuICAgICAgICAgICAgdGhpcy5fbGlzdGVuTG9naW5NZXNzYWdlKHJlbmRlcmVyKSxcbiAgICAgICAgICAgIHRoaXMuX2xpc3RlbkNoYW5nZXNGcm9tT3RoZXJXaW5kb3dzKHJlbmRlcmVyKVxuICAgICAgICBdO1xuXG4gICAgICAgIHRoaXMuX2N1cnJlbnRTdGF0ZSA9IG51bGw7XG4gICAgICAgIHRoaXMuX3VwZGF0ZVVzZXIoKTsgLy8gVE9ETzogZXhwZXJpbWVudCB3aXRoIHNldFRpbWVPdXRcbiAgICB9XG5cbiAgICBwdWJsaWMgbmdPbkRlc3Ryb3koKSB7XG4gICAgICAgIHRoaXMuX3VubGlzdGVuRXZlbnRzLmZvckVhY2goZm4gPT4gZm4oKSk7XG4gICAgfVxuXG4gICAgcHVibGljIHVzZXIoKTogT2JzZXJ2YWJsZSA8IFVzZXIgfCBudWxsID4ge1xuICAgICAgICByZXR1cm4gdGhpcy5fdXNlci5hc09ic2VydmFibGUoKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGdW5jdGlvbnMgdGhhdCBvcGVucyBhIHdpbmRvdyBpbnN0ZWFkIG9mIGEgdGFiLlxuICAgICAqXG4gICAgICogU2VlIG1ldGhvZCBfZmlsdGVyTG9naW5PcHRpb25zIHJlZ2FyZGluZyBzZWN1cml0eSByaXNrcyBvZiBjZXJ0YWluXG4gICAgICogTG9naW5PcHRpb25zLlxuICAgICAqXG4gICAgICogQHBhcmFtIGxvZ2luT3B0aW9ucyBPcHRpb25zIHBhc3NlZCBhcyBVUkwgcGFyYW1ldGVycyB0byB0aGUgU1NPLlxuICAgICAqIEBwYXJhbSB3aWR0aCBQaXhlbCB3aWR0aCBvZiB0aGUgbG9naW4gd2luZG93LlxuICAgICAqIEBwYXJhbSBoZWlnaHQgUGl4ZWwgaGVpZ2h0IG9mIHRoZSBsb2dpbiB3aW5kb3cuXG4gICAgICogQHBhcmFtIHRvcCBQb3NpdGlvbiBvZiB0aGUgdG9wIGNvcm5lcnMuIElmIGl0IGlzIGEgbmVnYXRpdmVcbiAgICAgKiAgICAgICAgICAgICBudW1iZXIgaXQgY2VudHJlcyB0aGUgbG9naW4gd2luZG93IG9uIHRoZSBzY3JlZW4uXG4gICAgICogQHBhcmFtIGxlZnQgUG9zaXRpb24gb2YgdGhlIGxlZnQgY29ybmVycy4gSWYgaXQgaXMgYSBuZWdhdGl2ZVxuICAgICAqICAgICAgICAgICAgIG51bWJlciBpdCBjZW50cmVzIHRoZSBsb2dpbiB3aW5kb3cgb24gdGhlIHNjcmVlbi5cbiAgICAgKi9cbiAgICBwdWJsaWMgb3BlbkxvZ2luV2luZG93KG9wdGlvbnM/OiBMb2dpbk9wdGlvbnMsIHdpZHRoID0gNjUwLCBoZWlnaHQgPSAxMDAwLCBsZWZ0ID0gLTEsIHRvcCA9IC0xKSB7XG4gICAgICAgIGlmIChsZWZ0IDwgMCkge1xuICAgICAgICAgICAgY29uc3Qgc2NyZWVuV2lkdGggPSBzY3JlZW4ud2lkdGg7XG4gICAgICAgICAgICBpZiAoc2NyZWVuV2lkdGggPiB3aWR0aCkge1xuICAgICAgICAgICAgICAgIGxlZnQgPSBNYXRoLnJvdW5kKChzY3JlZW5XaWR0aCAtIHdpZHRoKSAvIDIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmICh0b3AgPCAwKSB7XG4gICAgICAgICAgICBjb25zdCBzY3JlZW5IZWlnaHQgPSBzY3JlZW4uaGVpZ2h0O1xuICAgICAgICAgICAgaWYgKHNjcmVlbkhlaWdodCA+IGhlaWdodCkge1xuICAgICAgICAgICAgICAgIHRvcCA9IE1hdGgucm91bmQoKHNjcmVlbkhlaWdodCAtIGhlaWdodCkgLyAyKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IHdpbmRvd09wdGlvbnMgPSBbXG4gICAgICAgICAgICBgd2lkdGg9JHt3aWR0aH1gLFxuICAgICAgICAgICAgYGhlaWdodD0ke2hlaWdodH1gLFxuICAgICAgICAgICAgYGxlZnQ9JHtsZWZ0fWAsXG4gICAgICAgICAgICBgdG9wPSR7dG9wfWAsXG4gICAgICAgICAgICAncGVyc29uYWxiYXI9bm8nLFxuICAgICAgICAgICAgJ3Rvb2xiYXI9bm8nLFxuICAgICAgICAgICAgJ3Njcm9sbGJhcnM9eWVzJyxcbiAgICAgICAgICAgICdyZXNpemFibGU9eWVzJyxcbiAgICAgICAgICAgICdkaXJlY3Rvcmllcz1ubycsXG4gICAgICAgICAgICAnbG9jYXRpb249bm8nLFxuICAgICAgICAgICAgJ21lbnViYXI9bm8nLFxuICAgICAgICAgICAgJ3RpdGxlYmFyPW5vJyxcbiAgICAgICAgICAgICd0b29sYmFyPW5vJ1xuICAgICAgICBdO1xuXG4gICAgICAgIGNvbnN0IGxvZ2luV2luZG93ID0gd2luZG93Lm9wZW4odGhpcy5nZXRTU09VUkwob3B0aW9ucyksICdTaWduIGluIHRvIEVsaXhpcicsIHdpbmRvd09wdGlvbnMuam9pbignLCcpKTtcbiAgICAgICAgaWYgKGxvZ2luV2luZG93KSB7XG4gICAgICAgICAgICBsb2dpbldpbmRvdy5mb2N1cygpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWQgdXNlIG9wZW5Mb2dpbldpbmRvdyBtZXRob2QgaW5zdGVhZCAodG9wIGFuZCBsZWZ0IGFyZ3VtZW50cyBhcmUgaW52ZXJ0ZWQgaW4gdGhlIG5ldyBtZXRob2QpXG4gICAgICogc2luY2UgdmVyc2lvbiAxLjAuMC1iZXRhLjUuXG4gICAgICogd2luZG93T3BlbiB3aWxsIGJlIGRlbGV0ZWQgaW4gdmVyc2lvbiAxLjAuMFxuICAgICAqL1xuICAgIHB1YmxpYyB3aW5kb3dPcGVuKG9wdGlvbnM/OiBMb2dpbk9wdGlvbnMsIHdpZHRoID0gNjUwLCBoZWlnaHQgPSAxMDAwLCB0b3AgPSAtMSwgbGVmdCA9IC0xKSB7XG4gICAgICAgIHRoaXMuX2RlcHJlY2F0aW9uV2FybmluZygnd2luZG93T3BlbicsICdvcGVuTG9naW5XaW5kb3cnKTtcbiAgICAgICAgdGhpcy5vcGVuTG9naW5XaW5kb3cob3B0aW9ucywgd2lkdGgsIGhlaWdodCwgbGVmdCwgdG9wKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGdW5jdGlvbnMgdGhhdCBvcGVucyBhIHRhYiAoaW4gbW9kZXJuIGJyb3dzZXIpLlxuICAgICAqXG4gICAgICogU2VlIG1ldGhvZCBfZmlsdGVyTG9naW5PcHRpb25zIHJlZ2FyZGluZyBzZWN1cml0eSByaXNrcyBvZiBjZXJ0YWluXG4gICAgICogTG9naW5PcHRpb25zLlxuICAgICAqXG4gICAgICogQHBhcmFtIGxvZ2luT3B0aW9ucyBPcHRpb25zIHBhc3NlZCBhcyBVUkwgcGFyYW1ldGVycyB0byB0aGUgU1NPLlxuICAgICAqL1xuICAgIHB1YmxpYyBvcGVuTG9naW5UYWIob3B0aW9ucz86IExvZ2luT3B0aW9ucykge1xuICAgICAgICBjb25zdCBsb2dpbldpbmRvdyA9IHdpbmRvdy5vcGVuKHRoaXMuZ2V0U1NPVVJMKG9wdGlvbnMpLCAnU2lnbiBpbiB0byBFbGl4aXInKTtcbiAgICAgICAgaWYgKGxvZ2luV2luZG93KSB7XG4gICAgICAgICAgICBsb2dpbldpbmRvdy5mb2N1cygpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQGRlcHJlY2F0ZWQgdXNlIG9wZW5Mb2dpblRhYiBtZXRob2QgaW5zdGVhZFxuICAgICAqIHNpbmNlIHZlcnNpb24gMS4wLjAtYmV0YS41LlxuICAgICAqIHRhYk9wZW4gd2lsbCBiZSBkZWxldGVkIGluIHZlcnNpb24gMS4wLjBcbiAgICAgKi9cbiAgICBwdWJsaWMgdGFiT3BlbihvcHRpb25zPzogTG9naW5PcHRpb25zKSB7XG4gICAgICAgIHRoaXMuX2RlcHJlY2F0aW9uV2FybmluZygndGFiT3BlbicsICdvcGVuTG9naW5UYWInKTtcbiAgICAgICAgdGhpcy5vcGVuTG9naW5UYWIob3B0aW9ucyk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUHJvZHVjZXMgYSBVUkwgdGhhdCBhbGxvd3MgbG9nZ2luZyBpbnRvIHRoZSBzaW5nbGUgc2lnbiBvbiAoU1NPKSBwYWdlLlxuICAgICAqIFRoZSBVUkwgY2FucyBiZSBvcGVuZWQgaW4gYSBuZXcgdGFiIHVzaW5nIHRhcmdldD1cIl9ibGFua1wiLFxuICAgICAqIG9yIGluIGEgbmV3IHdpbmRvdyB1c2luZyB3aW5kb3cub3BlbigpLlxuICAgICAqXG4gICAgICogU2VlIG1ldGhvZCBfZmlsdGVyTG9naW5PcHRpb25zIHJlZ2FyZGluZyBzZWN1cml0eSByaXNrcyBvZiBjZXJ0YWluXG4gICAgICogTG9naW5PcHRpb25zLlxuICAgICAqXG4gICAgICogQHBhcmFtICBsb2dpbk9wdGlvbnMgT3B0aW9ucyBwYXNzZWQgYXMgVVJMIHBhcmFtZXRlcnMgdG8gdGhlIFNTTy5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBTU08gVVJMLlxuICAgICAqXG4gICAgICovXG4gICAgcHVibGljIGdldFNTT1VSTChvcHRpb25zPzogTG9naW5PcHRpb25zKTogc3RyaW5nIHtcbiAgICAgICAgY29uc3QgZnJhZ21lbnRzID0gdGhpcy5fZm9ybWF0RnJhZ21lbnRzKHtcbiAgICAgICAgICAgICdmcm9tJzogdGhpcy5fZG9tYWluLFxuICAgICAgICAgICAgLi4ub3B0aW9uc1xuICAgICAgICB9KTtcbiAgICAgICAgcmV0dXJuIGAke3RoaXMuX2FwcFVSTH0vc3NvJHtmcmFnbWVudHN9YDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGdW5jdGlvbnMgdGhhdCBsb2dzIG91dCB0aGUgdXNlci5cbiAgICAgKiBJdCB0cmlnZ2VycyB0aGUgbG9nb3V0IGNhbGxiYWNrcy5cbiAgICAgKiBJdCBpcyBhbiBhcnJvdyBmdW5jdGlvbiAobGFtYmRhKSBiZWNhdXNlIGluIHRoYXQgd2F5IGl0IGhhcyBhIHJlZmVyZW5jZVxuICAgICAqIHRvICd0aGlzJyB3aGVuIHVzZWQgaW4gc2V0VGltZW91dCBjYWxsLlxuICAgICAqL1xuICAgIHB1YmxpYyBsb2dPdXQoKSB7XG4gICAgICAgIHRoaXMuX3N0b3JhZ2VSZW1vdmVyKCk7XG4gICAgICAgIHRoaXMuX3VwZGF0ZVVzZXIoKTtcblxuICAgICAgICAvLyBUcmlnZ2VycyB1cGRhdGluZyBvdGhlciB3aW5kb3dzXG4gICAgICAgIHRoaXMuX2NvbW1LZXlVcGRhdGVyKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlIEFBUCBhY2NvdW50XG4gICAgICpcbiAgICAgKiBAcmV0dXJucyB1aWQgb2YgdGhlIG5ldyB1c2VyXG4gICAgICovXG4gICAgcHVibGljIGNyZWF0ZUFBUGFjY291bnQobmV3VXNlcjoge1xuICAgICAgICBuYW1lPzogc3RyaW5nLFxuICAgICAgICB1c2VybmFtZTogc3RyaW5nLFxuICAgICAgICBwYXNzd29yZDogc3RyaW5nLFxuICAgICAgICBlbWFpbD86IHN0cmluZyxcbiAgICAgICAgb3JnYW5pemF0aW9uPzogc3RyaW5nXG4gICAgfSk6IE9ic2VydmFibGUgPCBzdHJpbmcgPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9odHRwLnBvc3QodGhpcy5fYXV0aFVSTCwgbmV3VXNlciwge1xuICAgICAgICAgICAgcmVzcG9uc2VUeXBlOiAndGV4dCdcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTG9naW4gZGlyZWN0bHkgdGhyb3VnaCB0aGUgQUFQXG4gICAgICpcbiAgICAgKiBTZWUgbWV0aG9kIF9maWx0ZXJMb2dpbk9wdGlvbnMgcmVnYXJkaW5nIHNlY3VyaXR5IHJpc2tzIG9mIGNlcnRhaW5cbiAgICAgKiBMb2dpbk9wdGlvbnMuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyB0cnVlIGlmIHRoZSB1c2VyIHN1Y2Nlc3NmdWxseSBsb2dpbiwgb3RoZXJ3aXNlIGZhbHNlXG4gICAgICovXG4gICAgcHVibGljIGxvZ2luQUFQKFxuICAgICAgICB1c2VyOiB7XG4gICAgICAgICAgICB1c2VybmFtZTogc3RyaW5nLFxuICAgICAgICAgICAgcGFzc3dvcmQ6IHN0cmluZyxcbiAgICAgICAgfSwgb3B0aW9ucz86IExvZ2luT3B0aW9uc1xuICAgICk6IE9ic2VydmFibGUgPCBib29sZWFuID4ge1xuICAgICAgICBjb25zdCBmcmFnbWVudHMgPSB0aGlzLl9mb3JtYXRGcmFnbWVudHMob3B0aW9ucyk7XG4gICAgICAgIHJldHVybiB0aGlzLl9odHRwLmdldChgJHt0aGlzLl9hdXRoVVJMfSR7ZnJhZ21lbnRzfWAsIHtcbiAgICAgICAgICAgIGhlYWRlcnM6IHRoaXMuX2NyZWF0ZUF1dGhIZWFkZXIodXNlciksXG4gICAgICAgICAgICByZXNwb25zZVR5cGU6ICd0ZXh0J1xuICAgICAgICB9KS5waXBlKFxuICAgICAgICAgICAgdGFwKHRva2VuID0+IHtcbiAgICAgICAgICAgICAgICB0aGlzLl9zdG9yYWdlUmVtb3ZlcigpO1xuICAgICAgICAgICAgICAgIHRoaXMuX3N0b3JhZ2VVcGRhdGVyKHRva2VuKTtcbiAgICAgICAgICAgICAgICB0aGlzLl91cGRhdGVVc2VyKCk7XG5cbiAgICAgICAgICAgICAgICAvLyBUcmlnZ2VycyB1cGRhdGluZyBvdGhlciB3aW5kb3dzXG4gICAgICAgICAgICAgICAgdGhpcy5fY29tbUtleVVwZGF0ZXIoKTtcbiAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgbWFwKEJvb2xlYW4pLFxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENoYW5nZSBwYXNzd29yZCBBQVAgYWNjb3VudFxuICAgICAqXG4gICAgICogQHJldHVybnMgdHJ1ZSB3aGVuIHBhc3N3b3JkIGlzIHN1Y2Nlc3NmdWxseSBjaGFuZ2VkXG4gICAgICovXG4gICAgcHVibGljIGNoYW5nZVBhc3N3b3JkQUFQKHtcbiAgICAgICAgdXNlcm5hbWUsXG4gICAgICAgIG9sZFBhc3N3b3JkLFxuICAgICAgICBuZXdQYXNzd29yZFxuICAgIH06IHtcbiAgICAgICAgdXNlcm5hbWU6IHN0cmluZyxcbiAgICAgICAgb2xkUGFzc3dvcmQ6IHN0cmluZyxcbiAgICAgICAgbmV3UGFzc3dvcmQ6IHN0cmluZyxcbiAgICB9KTogT2JzZXJ2YWJsZSA8IGJvb2xlYW4gPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9odHRwLnBhdGNoKHRoaXMuX2F1dGhVUkwsIHtcbiAgICAgICAgICAgIHVzZXJuYW1lLFxuICAgICAgICAgICAgcGFzc3dvcmQ6IG5ld1Bhc3N3b3JkXG4gICAgICAgIH0sIHtcbiAgICAgICAgICAgIGhlYWRlcnM6IHRoaXMuX2NyZWF0ZUF1dGhIZWFkZXIoe1xuICAgICAgICAgICAgICAgIHVzZXJuYW1lLFxuICAgICAgICAgICAgICAgIHBhc3N3b3JkOiBvbGRQYXNzd29yZFxuICAgICAgICAgICAgfSlcbiAgICAgICAgfSkucGlwZShcbiAgICAgICAgICAgIG1hcChyZXNwb25zZSA9PiB0cnVlKSAvLyByZXNwb25zZSBpcyBlbXB0eSwgYnV0IGlmIGl0IHJlYWNoZXMgdGhpcyBwb2ludCB0aGUgcmVxdWVzdCBoYXMgc3VjY2Vzc2Z1bGx5IGNvbXBsZXRlZFxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEFkZCBhIGNhbGxiYWNrIHRvIHRoZSBMb2dJbiBldmVudC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBjYWxsYmFjayBUaGUgRnVuY3Rpb24gY2FsbGVkIHdoZW4gdGhlIGxvZ2luIGV2ZW50IGlzIHRyaWdnZXJlZCBhbmQgdGhlXG4gICAgICogICAgSldUIHRva2VuIGlzIHJlY2VpdmVkIGFuZCBhY2NlcHRlZC5cbiAgICAgKlxuICAgICAqIEByZXR1cm5zIFRoZSBldmVudCByZWdpc3RyYXRpb24gaWQgKG5lY2Vzc2FyeSB0byB1bnJlZ2lzdGVyIHRoZSBldmVudCkuXG4gICAgICovXG4gICAgcHVibGljIGFkZExvZ0luRXZlbnRMaXN0ZW5lcihjYWxsYmFjazogRnVuY3Rpb24pOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gdGhpcy5fbG9naW5DYWxsYmFja3MucHVzaChjYWxsYmFjayk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUmVtb3ZlIGEgY2FsbGJhY2sgZnJvbSB0aGUgTG9nSW4gZXZlbnQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gaWQgVGhlIGlkIGdpdmVuIHdoZW4gZXZlbnQgbGlzdGVuZXIgd2FzIGFkZGVkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgdHJ1ZSB3aGVuIHJlbW92ZSBzdWNjZXNzZnVsbHksIGZhbHNlIG90aGVyd2lzZS5cbiAgICAgKi9cbiAgICBwdWJsaWMgcmVtb3ZlTG9nSW5FdmVudExpc3RlbmVyKGlkOiBudW1iZXIpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuIGRlbGV0ZSB0aGlzLl9sb2dpbkNhbGxiYWNrc1tpZCAtIDFdO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIEFkZCBhIGNhbGxiYWNrIHRvIHRoZSBMb2dPdXQgZXZlbnQuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gY2FsbGJhY2sgVGhlIEZ1bmN0aW9uIGNhbGxlZCB3aGVuIHRoZSBsb2dvdXQgZXZlbnQgaXMgdHJpZ2dlcmVkIGFuZCB0aGVcbiAgICAgKiAgICBKV1QgdG9rZW4gaXMgcmVjZWl2ZWQgYW5kIGFjY2VwdGVkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgVGhlIHJlZ2lzdHJhdGlvbiBpZCAobmVjZXNzYXJ5IHRvIHVucmVnaXN0ZXIgdGhlIGV2ZW50KS5cbiAgICAgKi9cbiAgICBwdWJsaWMgYWRkTG9nT3V0RXZlbnRMaXN0ZW5lcihjYWxsYmFjazogRnVuY3Rpb24pOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gdGhpcy5fbG9nb3V0Q2FsbGJhY2tzLnB1c2goY2FsbGJhY2spO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJlbW92ZSBhIGNhbGxiYWNrIGZyb20gdGhlIExvZ091dCBldmVudC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBpZCBUaGUgaWQgZ2l2ZW4gd2hlbiBldmVudCBsaXN0ZW5lciB3YXMgYWRkZWQuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyB0cnVlIHdoZW4gcmVtb3ZlIHN1Y2Nlc3NmdWxseSwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgICAqL1xuICAgIHB1YmxpYyByZW1vdmVMb2dPdXRFdmVudExpc3RlbmVyKGlkOiBudW1iZXIpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuIGRlbGV0ZSB0aGlzLl9sb2dvdXRDYWxsYmFja3NbaWQgLSAxXTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSZWZyZXNoIHRva2VuXG4gICAgICpcbiAgICAgKiBAcmV0dXJucyB0cnVlIHdoZW4gdG9rZW4gaXMgc3VjY2Vzc2Z1bGx5IHJlZnJlc2hlZFxuICAgICAqL1xuICAgIHB1YmxpYyByZWZyZXNoKCk6IE9ic2VydmFibGUgPCBib29sZWFuID4ge1xuICAgICAgICByZXR1cm4gdGhpcy5faHR0cC5nZXQodGhpcy5fdG9rZW5VUkwsIHtcbiAgICAgICAgICAgIHJlc3BvbnNlVHlwZTogJ3RleHQnXG4gICAgICAgIH0pLnBpcGUoXG4gICAgICAgICAgICB0YXAodG9rZW4gPT4ge1xuICAgICAgICAgICAgICAgIHRoaXMuX3N0b3JhZ2VSZW1vdmVyKCk7XG4gICAgICAgICAgICAgICAgdGhpcy5fc3RvcmFnZVVwZGF0ZXIodG9rZW4pO1xuICAgICAgICAgICAgICAgIHRoaXMuX3VwZGF0ZVVzZXIoKTtcblxuICAgICAgICAgICAgICAgIC8vIFRyaWdnZXJzIHVwZGF0aW5nIG90aGVyIHdpbmRvd3NcbiAgICAgICAgICAgICAgICB0aGlzLl9jb21tS2V5VXBkYXRlcigpO1xuICAgICAgICAgICAgfSksXG4gICAgICAgICAgICBtYXAoQm9vbGVhbilcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGb3JtYXQgYW5kIGZpbHRlciBmcmFnbWVudCBvcHRpb25zXG4gICAgICpcbiAgICAgKiBAcGFyYW1zIG9wdGlvbnNcbiAgICAgKlxuICAgICAqIEByZXR1cm5zIGZyYWdtZW50IHN0cmluZ1xuICAgICAqL1xuICAgIHByaXZhdGUgX2Zvcm1hdEZyYWdtZW50cyhvcHRpb25zPzogTG9naW5PcHRpb25zKSB7XG4gICAgICAgIGlmIChvcHRpb25zKSB7XG4gICAgICAgICAgICB0aGlzLl9maWx0ZXJMb2dpbk9wdGlvbnMob3B0aW9ucyk7XG4gICAgICAgICAgICByZXR1cm4gJz8nICsgT2JqZWN0LmVudHJpZXMob3B0aW9ucylcbiAgICAgICAgICAgICAgICAubWFwKChba2V5LCB2YWx1ZV0pID0+IGAke2tleX09JHt2YWx1ZX1gKS5qb2luKCcmJyk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gJyc7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRmlsdGVycyBvcHRpb25zIHRoYXQgYXJlIHVuc2VjdXJlLlxuICAgICAqXG4gICAgICogU2VlIHRoZSBhZHZhbmNlIG9wdGlvbnMgdGhhdCBjYW4gYmUgcmVxdWVzdGVkIHRocm91Z2ggdGhlIG9wdGlvbnMgcGFyYW1ldGVyOlxuICAgICAqIGh0dHBzOi8vYXBpLmFhaS5lYmkuYWMudWsvZG9jcy9hdXRoZW50aWNhdGlvbi9hdXRoZW50aWNhdGlvbi5pbmRleC5odG1sI19jb21tb25fYXR0cmlidXRlc1xuICAgICAqXG4gICAgICogVGhlIHRpbWUgdG8gbGl2ZSBwYXJhbWVudGVyICh0dGwpIGRlZmF1bHQgdmFsdWUgaXMgNjAgbWludXRlcy4gSXQgaXMgYVxuICAgICAqIGJpZyBzZWN1cml0eSByaXNrIHRvIHJlcXVlc3QgbG9uZ2VyIHR0bC4gSWYgYSB0aGlyZCBwYXJ0eSBnZXRzIGhvbGQgb2ZcbiAgICAgKiBzdWNoIHRva2VuLCBtZWFucyB0aGF0IHRoZXkgY291bGQgdXNlIGl0IGZvciBhIGRheSwgd2VlaywgeWVhclxuICAgICAqIChlc3NlbnRpYWxseSwgbGlrZSBoYXZpbmcgdGhlIHVzZXJuYW1lL3Bhc3N3b3JkKS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSAgbG9naW5PcHRpb25zIE9wdGlvbnMgcGFzc2VkIGFzIFVSTCBwYXJhbWV0ZXJzIHRvIHRoZSBTU08uXG4gICAgICpcbiAgICAgKlxuICAgICAqL1xuICAgIHByaXZhdGUgX2ZpbHRlckxvZ2luT3B0aW9ucyhvcHRpb25zOiBMb2dpbk9wdGlvbnMpIHtcbiAgICAgICAgaWYgKE9iamVjdC5rZXlzKG9wdGlvbnMpLmluZGV4T2YoJ3R0bCcpID4gLTEpIHtcbiAgICAgICAgICAgIGNvbnN0IHR0bDogbnVtYmVyID0gK29wdGlvbnNbJ3R0bCddO1xuICAgICAgICAgICAgY29uc3Qgc29mdExpbWl0ID0gNjA7XG4gICAgICAgICAgICBjb25zdCBoYXJkTGltaXQgPSA2MCAqIDI0O1xuICAgICAgICAgICAgaWYgKHR0bCA+IGhhcmRMaW1pdCkge1xuICAgICAgICAgICAgICAgIHRocm93IChuZXcgRXJyb3IoYExvZ2luIHJlcXVlc3RlZCB3aXRoIGFuIGV4cGlyYXRpb24gbG9uZ2VyIHRoYW4gJHtoYXJkTGltaXR9IG1pbnV0ZXMhIFRoaXMgaXMgbm90IGFsbG93ZWQuYCkpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKHR0bCA+IHNvZnRMaW1pdCkge1xuICAgICAgICAgICAgICAgIHdpbmRvdy5jb25zb2xlLndhcm4oYExvZ2luIHJlcXVlc3RlZCB3aXRoIGFuIGV4cGlyYXRpb24gbG9uZ2VyIHRoYW4gJHtzb2Z0TGltaXR9IG1pbnV0ZXMhYCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIEF1dGhvcml6YXRpb24gaGVhZGVyXG4gICAgICpcbiAgICAgKiBAcGFyYW0gb2JqZWN0IHdpdGggdXNlcm5hbWUgYW5kIHBhc3N3b3JkLlxuICAgICAqXG4gICAgICogQHJldHVybnMgTmV3IGF1dGhvcml6YXRpb24gaGVhZGVyXG4gICAgICovXG4gICAgcHJpdmF0ZSBfY3JlYXRlQXV0aEhlYWRlcih7XG4gICAgICAgIHVzZXJuYW1lLFxuICAgICAgICBwYXNzd29yZFxuICAgIH06IHtcbiAgICAgICAgdXNlcm5hbWU6IHN0cmluZyxcbiAgICAgICAgcGFzc3dvcmQ6IHN0cmluZyxcbiAgICB9KSB7XG4gICAgICAgIGNvbnN0IGF1dGhUb2tlbiA9IGJ0b2EoYCR7dXNlcm5hbWV9OiR7cGFzc3dvcmR9YCk7XG4gICAgICAgIHJldHVybiBuZXcgSHR0cEhlYWRlcnMoe1xuICAgICAgICAgICAgJ0F1dGhvcml6YXRpb24nOiBgQmFzaWMgJHthdXRoVG9rZW59YFxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBMaXN0ZW4gZm9yIGxvZ2luIG1lc3NhZ2VzIGZyb20gb3RoZXIgd2luZG93cy5cbiAgICAgKiBUaGVzZSBtZXNzYWdlcyBjb250YWluIHRoZSB0b2tlbnMgZnJvbSB0aGUgQUFQLlxuICAgICAqIElmIGEgdG9rZW4gaXMgcmVjZWl2ZWQgdGhlbiB0aGUgY2FsbGJhY2tzIGFyZSB0cmlnZ2VyZWQuXG4gICAgICovXG4gICAgcHJpdmF0ZSBfbGlzdGVuTG9naW5NZXNzYWdlKHJlbmRlcmVyOiBSZW5kZXJlcjIpOiBGdW5jdGlvbiB7XG4gICAgICAgIHJldHVybiByZW5kZXJlci5saXN0ZW4oJ3dpbmRvdycsICdtZXNzYWdlJywgKGV2ZW50OiBNZXNzYWdlRXZlbnQpID0+IHtcbiAgICAgICAgICAgIGlmICghdGhpcy5fbWVzc2FnZUlzQWNjZXB0YWJsZShldmVudCkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB0aGlzLl9zdG9yYWdlVXBkYXRlcihldmVudC5kYXRhKTtcblxuICAgICAgICAgICAgLy8gSSBkb24ndCBrbm93IGhvdyB0byB0eXBlIGd1YXJkIGV2ZW50LnNvdXJjZVxuICAgICAgICAgICAgLy8gVGhpcyBkb2Vzbid0IHdvcmtcbiAgICAgICAgICAgIC8vIGlmIChldmVudC5zb3VyY2UgaW5zdGFuY2VvZiBXaW5kb3cpIHtcbiAgICAgICAgICAgIGlmIChldmVudC5zb3VyY2UpIHtcbiAgICAgICAgICAgICAgICAoZXZlbnQuc291cmNlIGFzIFdpbmRvdykuY2xvc2UoKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5fdXBkYXRlVXNlcigpO1xuXG4gICAgICAgICAgICAvLyBUcmlnZ2VycyB1cGRhdGluZyBvdGhlciB3aW5kb3dzXG4gICAgICAgICAgICB0aGlzLl9jb21tS2V5VXBkYXRlcigpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKiogTGlzdGVuIHRvIGNoYW5nZXMgaW4gdGhlIHRva2VuIGZyb20gKm90aGVyKiB3aW5kb3dzLlxuICAgICAqXG4gICAgICogRm9yIGludGVyLXdpbmRvdyBjb21tdW5pY2F0aW9uIG1lc3NhZ2VzIGFyZSB0cmFuc21pdHRlZCB0cm91Z2ggY2hhbmdlc1xuICAgICAqIG9uIGEgZHVtbXkgc3RvcmFnZ