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
JavaScript
/**
* @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