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