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).

964 lines 67.6 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ import * as tslib_1 from "tslib"; 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; } var AuthService = /** @class */ (function () { function AuthService(_rendererFactory, _tokenService, _http, config) { var _this = this; 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 = function () { return 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 = function () { return config.tokenUpdater(null); }; } /** @type {?} */ var renderer = this._rendererFactory.createRenderer(null, null); this._unlistenEvents = [ this._listenLoginMessage(renderer), this._listenChangesFromOtherWindows(renderer) ]; this._currentState = null; this._updateUser(); // TODO: experiment with setTimeOut } /** * @return {?} */ AuthService.prototype.ngOnDestroy = /** * @return {?} */ function () { this._unlistenEvents.forEach(function (fn) { return fn(); }); }; /** * @return {?} */ AuthService.prototype.user = /** * @return {?} */ function () { return this._user.asObservable(); }; /** * 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. */ /** * 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 {?} */ AuthService.prototype.openLoginWindow = /** * 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 {?} */ function (options, width, height, left, top) { if (width === void 0) { width = 650; } if (height === void 0) { height = 1000; } if (left === void 0) { left = -1; } if (top === void 0) { top = -1; } if (left < 0) { /** @type {?} */ var screenWidth = screen.width; if (screenWidth > width) { left = Math.round((screenWidth - width) / 2); } } if (top < 0) { /** @type {?} */ var screenHeight = screen.height; if (screenHeight > height) { top = Math.round((screenHeight - height) / 2); } } /** @type {?} */ var 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 {?} */ var 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 */ /** * @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 {?} */ AuthService.prototype.windowOpen = /** * @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 {?} */ function (options, 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; } 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 loginOptions Options passed as URL parameters to the SSO. */ /** * Functions that opens a tab (in modern browser). * * See method _filterLoginOptions regarding security risks of certain * LoginOptions. * * @param {?=} options * @return {?} */ AuthService.prototype.openLoginTab = /** * Functions that opens a tab (in modern browser). * * See method _filterLoginOptions regarding security risks of certain * LoginOptions. * * @param {?=} options * @return {?} */ function (options) { /** @type {?} */ var 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 */ /** * @deprecated use openLoginTab method instead * since version 1.0.0-beta.5. * tabOpen will be deleted in version 1.0.0 * @param {?=} options * @return {?} */ AuthService.prototype.tabOpen = /** * @deprecated use openLoginTab method instead * since version 1.0.0-beta.5. * tabOpen will be deleted in version 1.0.0 * @param {?=} options * @return {?} */ function (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 loginOptions Options passed as URL parameters to the SSO. * * @returns The SSO URL. * */ /** * 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) { /** @type {?} */ var fragments = this._formatFragments(tslib_1.__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. */ /** * 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._updateUser(); // Triggers updating other windows this._commKeyUpdater(); }; /** * Create AAP account * * @returns uid of the new user */ /** * Create AAP account * * @param {?} newUser * @return {?} uid of the new user */ AuthService.prototype.createAAPaccount = /** * Create AAP account * * @param {?} newUser * @return {?} uid of the new user */ function (newUser) { return this._http.post(this._authURL, newUser, { responseType: 'text' }); }; /** * Login directly through the AAP * * See method _filterLoginOptions regarding security risks of certain * LoginOptions. * * @returns true if the user successfully login, otherwise false */ /** * 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 */ AuthService.prototype.loginAAP = /** * 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 */ function (user, options) { var _this = this; /** @type {?} */ var fragments = this._formatFragments(options); return this._http.get("" + this._authURL + fragments, { headers: this._createAuthHeader(user), responseType: 'text' }).pipe(tap(function (token) { _this._storageRemover(); _this._storageUpdater(token); _this._updateUser(); // Triggers updating other windows _this._commKeyUpdater(); }), map(Boolean)); }; /** * Change password AAP account * * @returns true when password is successfully changed */ /** * Change password AAP account * * @param {?} __0 * @return {?} true when password is successfully changed */ AuthService.prototype.changePasswordAAP = /** * Change password AAP account * * @param {?} __0 * @return {?} true when password is successfully changed */ function (_a) { var username = _a.username, oldPassword = _a.oldPassword, newPassword = _a.newPassword; return this._http.patch(this._authURL, { username: username, password: newPassword }, { headers: this._createAuthHeader({ username: username, password: oldPassword }) }).pipe(map(function (response) { return 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. * * @returns The event registration id (necessary to unregister the event). */ /** * 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. * * @returns true when remove successfully, false otherwise. */ /** * 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. * * @returns The registration id (necessary to unregister the event). */ /** * 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. * * @returns true when remove successfully, false otherwise. */ /** * 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]; }; /** * Refresh token * * @returns true when token is successfully refreshed */ /** * Refresh token * * @return {?} true when token is successfully refreshed */ AuthService.prototype.refresh = /** * Refresh token * * @return {?} true when token is successfully refreshed */ function () { var _this = this; return this._http.get(this._tokenURL, { responseType: 'text' }).pipe(tap(function (token) { _this._storageRemover(); _this._storageUpdater(token); _this._updateUser(); // Triggers updating other windows _this._commKeyUpdater(); }), map(Boolean)); }; /** * Format and filter fragment options * * @params options * * @returns fragment string */ /** * Format and filter fragment options * * \@params options * * @private * @param {?=} options * @return {?} fragment string */ AuthService.prototype._formatFragments = /** * Format and filter fragment options * * \@params options * * @private * @param {?=} options * @return {?} fragment string */ function (options) { if (options) { this._filterLoginOptions(options); return '?' + Object.entries(options) .map(function (_a) { var _b = tslib_1.__read(_a, 2), key = _b[0], value = _b[1]; return 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). * * @param loginOptions Options passed as URL parameters to the SSO. * * */ /** * 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 {?} */ 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). * * @private * @param {?} options * @return {?} */ function (options) { if (Object.keys(options).indexOf('ttl') > -1) { /** @type {?} */ var ttl = +options['ttl']; /** @type {?} */ var softLimit = 60; /** @type {?} */ var 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 * * @param object with username and password. * * @returns New authorization header */ /** * Creates Authorization header * * @private * @param {?} __0 * @return {?} New authorization header */ AuthService.prototype._createAuthHeader = /** * Creates Authorization header * * @private * @param {?} __0 * @return {?} New authorization header */ function (_a) { var username = _a.username, password = _a.password; /** @type {?} */ var 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. */ /** * 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 {?} */ 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. * @private * @param {?} renderer * @return {?} */ function (renderer) { var _this = this; return renderer.listen('window', 'message', function (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. */ /** * 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 {?} */ 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. * @private * @param {?} renderer * @return {?} */ function (renderer) { var _this = this; return renderer.listen('window', 'storage', function (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. */ /** * 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 {?} */ 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. * @private * @param {?} event * @return {?} */ function (event) { return event.origin === this._appURL; }; /** * @private * @return {?} */ AuthService.prototype._updateUser = /** * @private * @return {?} */ function () { var _this = this; if (this._timeoutID) { window.clearTimeout(this._timeoutID); } if (this._tokenService.isTokenValid()) { /** @type {?} */ var 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: 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(function (callback) { return callback(); }); } // Schedule future logout event base on token expiration /** @type {?} */ var expireDate = (/** @type {?} */ (this._tokenService.getTokenExpirationDate())); // Coercing dates to numbers with the unary operator '+' /** @type {?} */ var delay = +expireDate - +new Date(); this._timeoutID = window.setTimeout(function () { return _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(function (callback) { return callback(); }); this._user.next(null); } this._currentState = null; } }; /** * @private * @param {?} claim * @return {?} */ AuthService.prototype._getClaim = /** * @private * @param {?} claim * @return {?} */ function (claim) { return this._tokenService.getClaim(claim, ''); }; /** * @private * @param {?} oldMethod * @param {?} newMethod * @return {?} */ AuthService.prototype._deprecationWarning = /** * @private * @param {?} oldMethod * @param {?} newMethod * @return {?} */ function (oldMethod, newMethod) { window.console.warn("Method '" + oldMethod + "' has been deprecated, please use '" + newMethod + "' method instead"); }; AuthService.decorators = [ { type: Injectable } ]; /** @nocollapse */ AuthService.ctorParameters = function () { return [ { type: RendererFactory2 }, { type: TokenService }, { type: HttpClient }, { type: undefined, decorators: [{ type: Inject, args: [AAP_CONFIG,] }] } ]; }; return AuthService; }()); export { AuthService }; 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6Im5nOi8vbmctZWJpLWF1dGhvcml6YXRpb24vIiwic291cmNlcyI6WyJhdXRoL2F1dGguc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLE9BQU8sRUFDSCxVQUFVLEVBQ1YsTUFBTSxFQUVOLGdCQUFnQixFQUVuQixNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQ0gsVUFBVSxFQUNWLFdBQVcsRUFFZCxNQUFNLHNCQUFzQixDQUFDO0FBSTlCLE9BQU8sRUFDSCxlQUFlLEdBQ2xCLE1BQU0sTUFBTSxDQUFDO0FBQ2QsT0FBTyxFQUVILEdBQUcsRUFDSCxHQUFHLEVBQ04sTUFBTSxnQkFBZ0IsQ0FBQztBQUV4QixPQUFPLEVBQ0gsVUFBVSxFQUViLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFDSCxZQUFZLEVBQ2YsTUFBTSxpQkFBaUIsQ0FBQzs7OztBQUV6QixrQ0FFQzs7OztBQUVELDBCQU1DOzs7SUFMRyxtQkFBWTs7SUFDWixvQkFBYTs7SUFDYix3QkFBaUI7O0lBQ2pCLHFCQUFjOztJQUNkLHFCQUFjOztBQUdsQjtJQTBCSSxxQkFDWSxnQkFBa0MsRUFDbEMsYUFBMkIsRUFDM0IsS0FBaUIsRUFDRyxNQUFrQjtRQUpsRCxpQkEyQkM7UUExQlcscUJBQWdCLEdBQWhCLGdCQUFnQixDQUFrQjtRQUNsQyxrQkFBYSxHQUFiLGFBQWEsQ0FBYztRQUMzQixVQUFLLEdBQUwsS0FBSyxDQUFZO1FBQ0csV0FBTSxHQUFOLE1BQU0sQ0FBWTtRQTNCMUMsVUFBSyxHQUFHLElBQUksZUFBZSxDQUFrQixJQUFJLENBQUMsQ0FBQztRQUNuRCxrQkFBYSxHQUFrQixJQUFJLENBQUMsQ0FBQyxxREFBcUQ7O1FBRTFGLG9CQUFlLEdBQWUsRUFBRSxDQUFDO1FBQ2pDLHFCQUFnQixHQUFlLEVBQUUsQ0FBQztRQUlsQyxlQUFVLEdBQWtCLElBQUksQ0FBQzs7O1FBWXhCLGlCQUFZLEdB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ICBJbmplY3RhYmxlLFxuICAgIEluamVjdCxcbiAgICBPbkRlc3Ryb3ksXG4gICAgUmVuZGVyZXJGYWN0b3J5MixcbiAgICBSZW5kZXJlcjJcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQge1xuICAgIEh0dHBDbGllbnQsXG4gICAgSHR0cEhlYWRlcnMsXG4gICAgSHR0cFJlc3BvbnNlXG59IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbi9odHRwJztcbmltcG9ydCB7XG4gICAgT2JzZXJ2YWJsZVxufSBmcm9tICdyeGpzJztcbmltcG9ydCB7XG4gICAgQmVoYXZpb3JTdWJqZWN0LFxufSBmcm9tICdyeGpzJztcbmltcG9ydCB7XG4gICAgZmlsdGVyLFxuICAgIG1hcCxcbiAgICB0YXBcbn0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuXG5pbXBvcnQge1xuICAgIEFBUF9DT05GSUcsXG4gICAgQXV0aENvbmZpZ1xufSBmcm9tICcuL2F1dGguY29uZmlnJztcbmltcG9ydCB7XG4gICAgVG9rZW5TZXJ2aWNlXG59IGZyb20gJy4vdG9rZW4uc2VydmljZSc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgTG9naW5PcHRpb25zIHtcbiAgICBba2V5OiBzdHJpbmddOiBzdHJpbmc7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgVXNlciB7XG4gICAgdWlkOiBzdHJpbmc7XG4gICAgbmFtZTogc3RyaW5nO1xuICAgIG5pY2tuYW1lOiBzdHJpbmc7XG4gICAgZW1haWw6IHN0cmluZztcbiAgICB0b2tlbjogc3RyaW5nO1xufVxuXG5ASW5qZWN0YWJsZSgpXG5leHBvcnQgY2xhc3MgQXV0aFNlcnZpY2UgaW1wbGVtZW50cyBPbkRlc3Ryb3kge1xuXG4gICAgcHJpdmF0ZSBfdXNlciA9IG5ldyBCZWhhdmlvclN1YmplY3QgPCBVc2VyIHwgbnVsbCA+IChudWxsKTtcbiAgICBwcml2YXRlIF9jdXJyZW50U3RhdGU6IHN0cmluZyB8IG51bGwgPSBudWxsOyAvLyBzdG9yZXMgdGhlIHN0cmluZyB0b2tlbiBvciBudWxsIG90aGVyd2lzZSAobG9nb3V0KVxuXG4gICAgcHJpdmF0ZSBfbG9naW5DYWxsYmFja3M6IEZ1bmN0aW9uW10gPSBbXTtcbiAgICBwcml2YXRlIF9sb2dvdXRDYWxsYmFja3M6IEZ1bmN0aW9uW10gPSBbXTtcblxuICAgIHByaXZhdGUgX3VubGlzdGVuRXZlbnRzOiBGdW5jdGlvbltdO1xuXG4gICAgcHJpdmF0ZSBfdGltZW91dElEOiBudW1iZXIgfCBudWxsID0gbnVsbDtcblxuICAgIC8vIENvbmZpZ3VyYXRpb25cbiAgICBwcml2YXRlIHJlYWRvbmx5IF9kb21haW46IHN0cmluZztcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9hcHBVUkw6IHN0cmluZztcbiAgICBwcml2YXRlIHJlYWRvbmx5IF90b2tlblVSTDogc3RyaW5nO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX2F1dGhVUkw6IHN0cmluZztcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9zdG9yYWdlVXBkYXRlcjogKG5ld1Rva2VuOiBhbnkpID0+IHZvaWQ7XG4gICAgcHJpdmF0ZSByZWFkb25seSBfc3RvcmFnZVJlbW92ZXI6ICgpID0+IHZvaWQ7XG5cbiAgICAvLyBUaGlzIHR3byBwcm9wZXJ0aWVzIGFyZSB1c2VkIGZvciBpbnRlci13aW5kb3cgY29tbXVuaWNhdGlvbi5cbiAgICAvLyBJdCBpcyBhY2hpZXZlIHRocm91Z2ggdGhlIHVwZGF0ZSBvZiB0aGUgZHVtbXkga2V5IHN0b3JhZ2UgJ19jb21tS2V5TmFtZSdcbiAgICBwcml2YXRlIHJlYWRvbmx5IF9jb21tS2V5TmFtZSA9ICdBbmd1bGFyQWFwQXV0aFVwZGF0ZWQnO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX2NvbW1LZXlVcGRhdGVyID0gKCkgPT4gbG9jYWxTdG9yYWdlLnNldEl0ZW0odGhpcy5fY29tbUtleU5hbWUsICcnICsgbmV3IERhdGUoKS5nZXRUaW1lKCkpO1xuXG4gICAgY29uc3RydWN0b3IoXG4gICAgICAgIHByaXZhdGUgX3JlbmRlcmVyRmFjdG9yeTogUmVuZGVyZXJGYWN0b3J5MixcbiAgICAgICAgcHJpdmF0ZSBfdG9rZW5TZXJ2aWNlOiBUb2tlblNlcnZpY2UsXG4gICAgICAgIHByaXZhdGUgX2h0dHA6IEh0dHBDbGllbnQsXG4gICAgICAgIEBJbmplY3QoQUFQX0NPTkZJRykgcHJpdmF0ZSBjb25maWc6IEF1dGhDb25maWdcbiAgICApIHtcbiAgICAgICAgdGhpcy5fZG9tYWluID0gZW5jb2RlVVJJQ29tcG9uZW50KHdpbmRvdy5sb2NhdGlvbi5vcmlnaW4pO1xuXG4gICAgICAgIHRoaXMuX2FwcFVSTCA9IGNvbmZpZy5hYXBVUkwucmVwbGFjZSgvXFwvJC8sICcnKTtcbiAgICAgICAgdGhpcy5fYXV0aFVSTCA9IGAke3RoaXMuX2FwcFVSTH0vYXV0aGA7XG4gICAgICAgIHRoaXMuX3Rva2VuVVJMID0gYCR7dGhpcy5fYXBwVVJMfS90b2tlbmA7XG5cbiAgICAgICAgdGhpcy5fc3RvcmFnZVVwZGF0ZXIgPSBjb25maWcudG9rZW5VcGRhdGVyO1xuICAgICAgICBpZiAoY29uZmlnLnRva2VuUmVtb3Zlcikge1xuICAgICAgICAgICAgdGhpcy5fc3RvcmFnZVJlbW92ZXIgPSBjb25maWcudG9rZW5SZW1vdmVyO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5fc3RvcmFnZVJlbW92ZXIgPSAoKSA9PiBjb25maWcudG9rZW5VcGRhdGVyKG51bGwpO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgcmVuZGVyZXIgPSB0aGlzLl9yZW5kZXJlckZhY3RvcnkuY3JlYXRlUmVuZGVyZXIobnVsbCwgbnVsbCk7XG4gICAgICAgIHRoaXMuX3VubGlzdGVuRXZlbnRzID0gW1xuICAgICAgICAgICAgdGhpcy5fbGlzdGVuTG9naW5NZXNzYWdlKHJlbmRlcmVyKSxcbiAgICAgICAgICAgIHRoaXMuX2xpc3RlbkNoYW5nZXNGcm9tT3RoZXJXaW5kb3dzKHJlbmRlcmVyKVxuICAgICAgICBdO1xuXG4gICAgICAgIHRoaXMuX2N1cnJlbnRTdGF0ZSA9IG51bGw7XG4gICAgICAgIHRoaXMuX3VwZGF0ZVVzZXIoKTsgLy8gVE9ETzogZXhwZXJpbWVudCB3aXRoIHNldFRpbWVPdXRcbiAgICB9XG5cbiAgICBwdWJsaWMgbmdPbkRlc3Ryb3koKSB7XG4gICAgICAgIHRoaXMuX3VubGlzdGVuRXZlbnRzLmZvckVhY2goZm4gPT4gZm4oKSk7XG4gICAgfVxuXG4gICAgcHVibGljIHVzZXIoKTogT2JzZXJ2YWJsZSA8IFVzZXIgfCBudWxsID4ge1xuICAgICAgICByZXR1cm4gdGhpcy5fdXNlci5hc09ic2VydmFibGUoKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGdW5jdGlvbnMgdGhhdCBvcGVucyBhIHdpbmRvdyBpbnN0ZWFkIG9mIGEgdGFiLlxuICAgICAqXG4gICAgICogU2VlIG1ldGhvZCBfZmlsdGVyTG9naW5PcHRpb25zIHJlZ2FyZGluZyBzZWN1cml0eSByaXNrcyBvZiBjZXJ0YWluXG4gICAgICogTG9naW5PcHRpb25zLlxuICAgICAqXG4gICAgICogQHBhcmFtIGxvZ2luT3B0aW9ucyBPcHRpb25zIHBhc3NlZCBhcyBVUkwgcGFyYW1ldGVycyB0byB0aGUgU1NPLlxuICAgICAqIEBwYXJhbSB3aWR0aCBQaXhlbCB3aWR0aCBvZiB0aGUgbG9naW4gd2luZG93LlxuICAgICAqIEBwYXJhbSBoZWlnaHQgUGl4ZWwgaGVpZ2h0IG9mIHRoZSBsb2dpbiB3aW5kb3cuXG4gICAgICogQHBhcmFtIHRvcCBQb3NpdGlvbiBvZiB0aGUgdG9wIGNvcm5lcnMuIElmIGl0IGlzIGEgbmVnYXRpdmVcbiAgICAgKiAgICAgICAgICAgICBudW1iZXIgaXQgY2VudHJlcyB0aGUgbG9naW4gd2luZG93IG9uIHRoZSBzY3JlZW4uXG4gICAgICogQHBhcmFtIGxlZnQgUG9zaXRpb24gb2YgdGhlIGxlZnQgY29ybmVycy4gSWYgaXQgaXMgYSBuZWdhdGl2ZVxuICAgICAqICAgICAgICAgICAgIG51bWJlciBpdCBjZW50cmVzIHRoZSBsb2dpbiB3aW5kb3cgb24gdGhlIHNjcmVlbi5cbiAgICAgKi9cbiAgICBwdWJsaWMgb3BlbkxvZ2luV2luZG93KG9wdGlvbnM/OiBMb2dpbk9wdGlvbnMsIHdpZHRoID0gNjUwLCBoZWlnaHQgPSAxMDAwLCBsZWZ0ID0gLTEsIHRvcCA9IC0xKSB7XG4gICAgICAgIGlmIChsZWZ0IDwgMCkge1xuICAgICAgICAgICAgY29uc3Qgc2NyZWVuV2lkdGggPSBzY3JlZW4ud2lkdGg7XG4gICAgICAgICAgICBpZiAoc2NyZWVuV2lkdGggPiB3aWR0aCkge1xuICAgICAgICAgICAgICAgIGxlZnQgPSBNYXRoLnJvdW5kKChzY3JlZW5XaWR0aCAtIHdpZHRoKSAvIDIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGlmICh0b3AgPCAwKSB7XG4gICAgICAgICAgICBjb25zdCBzY3JlZW5IZWlnaHQgPSBzY3JlZW4uaGVpZ2h0O1xuICAgICAgICAgICAgaWYgKHNjcmVlbkhlaWdodCA+IGhlaWdodCkge1xuICAgICAgICAgICAgICAgIHRvcCA9IE1hdGgucm91bmQoKHNjcmVlbkhlaWdodCAtIGhlaWdodCkgLyAyKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IHdpbmRvd09wdGlvbnMgPSBbXG4gICAgICAgICAgICBgd2lkdGg9JHt3aWR0aH1gLFxuICAgICAgICAgICAgYGhlaWdodD0ke