UNPKG

@secrethub/ngx-stripe

Version:

The core package for ngx-stripe, for using stripe.js in your application

651 lines (642 loc) 50.7 kB
import { InjectionToken, NgModule, Inject, Injectable, defineInjectable, inject } from '@angular/core'; import { Observable, BehaviorSubject } from 'rxjs'; import { __awaiter, __generator } from 'tslib'; import { filter, first } from 'rxjs/operators'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** * The public key that should be used for connecting to Stripe * @type {?} */ var STRIPE_PUBLIC_KEY = new InjectionToken('Stripe public key'); /** * Extra configuration options that can be used to further configure Stripe * @type {?} */ var STRIPE_OPTIONS = new InjectionToken('Optional configuration options'); /** * The version of stripe that should be used * @type {?} */ var STRIPE_VERSION = new InjectionToken('Stripe version to use'); /** * The location of the stripe javascript file * @type {?} */ var STRIPE_SCRIPT_LOCATION = 'https://js.stripe.com/'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** @enum {string} */ var SupportedVersions = { V3: 'v3', }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ var NgxStripeModule = /** @class */ (function () { function NgxStripeModule() { } /** * Creates a new instance of the NgxStripeModule * @param key - The public key that should be used to communicate with Stripe * @param options - Any options to configure StripeJS * @param [version=SupportedVersions.V3] - The version of Stripe that should be used */ /** * Creates a new instance of the NgxStripeModule * @param {?} key - The public key that should be used to communicate with Stripe * @param {?=} options - Any options to configure StripeJS * @param {?=} version * @return {?} */ NgxStripeModule.forRoot = /** * Creates a new instance of the NgxStripeModule * @param {?} key - The public key that should be used to communicate with Stripe * @param {?=} options - Any options to configure StripeJS * @param {?=} version * @return {?} */ function (key, options, version) { if (version === void 0) { version = SupportedVersions.V3; } return { ngModule: NgxStripeModule, providers: [ { provide: STRIPE_PUBLIC_KEY, useValue: key }, { provide: STRIPE_OPTIONS, useValue: options }, { provide: STRIPE_VERSION, useValue: version }, ], }; }; NgxStripeModule.decorators = [ { type: NgModule, args: [{ imports: [], },] } ]; return NgxStripeModule; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ var StripeLoader = /** @class */ (function () { function StripeLoader(version, options, key) { this.version = version; this.options = options; this.key = key; } /** * Fetches the stripe instance from the DOM * * @return The Stripe instance or null if it is not yet registered */ /** * Fetches the stripe instance from the DOM * * @param {?} key * @param {?} options * @return {?} The Stripe instance or null if it is not yet registered */ StripeLoader.getStripeInstance = /** * Fetches the stripe instance from the DOM * * @param {?} key * @param {?} options * @return {?} The Stripe instance or null if it is not yet registered */ function (key, options) { /** @type {?} */ var stripeInstance = window[StripeLoader.STRIPE_WINDOW_KEY]; if (stripeInstance) { return stripeInstance(key, options); } return null; }; /** * Checks whether or not StripeJS has been loaded */ /** * Checks whether or not StripeJS has been loaded * @return {?} */ StripeLoader.isStripeLoaded = /** * Checks whether or not StripeJS has been loaded * @return {?} */ function () { return !!window[StripeLoader.STRIPE_WINDOW_KEY]; }; /** * Injects the script tag into the body of the page in order to lazy * load the Stripe script * * @return Observable to indicate when the script has finished loading */ /** * Injects the script tag into the body of the page in order to lazy * load the Stripe script * * @return {?} Observable to indicate when the script has finished loading */ StripeLoader.prototype.loadScript = /** * Injects the script tag into the body of the page in order to lazy * load the Stripe script * * @return {?} Observable to indicate when the script has finished loading */ function () { var _this = this; return new Observable(function (observer) { if (!StripeLoader.isStripeLoaded()) { /** @type {?} */ var script = StripeLoader.constructScript(_this.getStripeScriptUrl()); script.onload = (function () { observer.next(StripeLoader.getStripeInstance(_this.key, _this.options)); observer.complete(); }); script.onerror = function () { observer.error('Failed to load the Stripe script!'); observer.complete(); }; document.body.appendChild(script); } else { observer.next(StripeLoader.getStripeInstance(_this.key, _this.options)); observer.complete(); } }); }; /** * Constructs the url that should be used for loading the Stripe script * @return URL to stripe script location */ /** * Constructs the url that should be used for loading the Stripe script * @return {?} URL to stripe script location */ StripeLoader.prototype.getStripeScriptUrl = /** * Constructs the url that should be used for loading the Stripe script * @return {?} URL to stripe script location */ function () { return STRIPE_SCRIPT_LOCATION + this.version + '/'; }; /** * Constructs a script element that loads javascript from the given url * @param url - The URL from which the javascript should be loaded * * @return A script element that can be attached to the DOM */ /** * Constructs a script element that loads javascript from the given url * @param {?} url - The URL from which the javascript should be loaded * * @return {?} A script element that can be attached to the DOM */ StripeLoader.constructScript = /** * Constructs a script element that loads javascript from the given url * @param {?} url - The URL from which the javascript should be loaded * * @return {?} A script element that can be attached to the DOM */ function (url) { /** @type {?} */ var script = document.createElement('script'); script.type = 'text/javascript'; script.async = true; script.defer = true; script.src = url; return script; }; /** * The key under which the stripe script is placed in the * window object * * @default 'Stripe' */ StripeLoader.STRIPE_WINDOW_KEY = 'Stripe'; StripeLoader.decorators = [ { type: Injectable, args: [{ providedIn: 'root', },] } ]; StripeLoader.ctorParameters = function () { return [ { type: SupportedVersions, decorators: [{ type: Inject, args: [STRIPE_VERSION,] }] }, { type: undefined, decorators: [{ type: Inject, args: [STRIPE_OPTIONS,] }] }, { type: String, decorators: [{ type: Inject, args: [STRIPE_PUBLIC_KEY,] }] } ]; }; /** @nocollapse */ StripeLoader.ngInjectableDef = defineInjectable({ factory: function StripeLoader_Factory() { return new StripeLoader(inject(STRIPE_VERSION), inject(STRIPE_OPTIONS), inject(STRIPE_PUBLIC_KEY)); }, token: StripeLoader, providedIn: "root" }); return StripeLoader; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ var StripeService = /** @class */ (function () { /** * Lazy load the StripeJS javascript file on first usage of the service * @param loader - The loader that should be used for loading StripeJS */ function StripeService(loader) { var _this = this; this.loader = loader; /** * A BehaviorSubject containing the StripeJS object * * Since the script is loaded Async we need an options for all our functions * to wait for stripe to have been loaded */ this.stripe$ = new BehaviorSubject(null); loader.loadScript() .subscribe(function (stripe) { return _this.stripe$.next(stripe); }, function () { throw new Error('Stripe could not be loaded!'); }); } /** * Creates a new stripe instance with the given key * @param key - The public key that should be used to communicate with Stripe * @param options - Any options to configure StripeJS */ /** * Creates a new stripe instance with the given key * @param {?} key - The public key that should be used to communicate with Stripe * @param {?=} options - Any options to configure StripeJS * @return {?} */ StripeService.prototype.changeKey = /** * Creates a new stripe instance with the given key * @param {?} key - The public key that should be used to communicate with Stripe * @param {?=} options - Any options to configure StripeJS * @return {?} */ function (key, options) { this.stripe$.next(StripeLoader.getStripeInstance(key, options)); }; /** * Configures the `Elements` object from StripeJS with the given options * @see https://stripe.com/docs/stripe-js/elements/quickstart#create-form * @param [options] - Any configuration options for the Elements object * @param [isIETFLocaleTag] - Whether or not the options `locale` is formatted as a IETFLocaleTag. * If true the locale will be formatted by this function * * @return Observable that resolves in an StripeJS ElementsCreator */ /** * Configures the `Elements` object from StripeJS with the given options * @see https://stripe.com/docs/stripe-js/elements/quickstart#create-form * @param {?=} options * @param {?=} isIETFLocaleTag * @return {?} Observable that resolves in an StripeJS ElementsCreator */ StripeService.prototype.getElementFactory = /** * Configures the `Elements` object from StripeJS with the given options * @see https://stripe.com/docs/stripe-js/elements/quickstart#create-form * @param {?=} options * @param {?=} isIETFLocaleTag * @return {?} Observable that resolves in an StripeJS ElementsCreator */ function (options, isIETFLocaleTag) { if (isIETFLocaleTag === void 0) { isIETFLocaleTag = false; } return __awaiter(this, void 0, void 0, function () { var stripe; return __generator(this, function (_a) { switch (_a.label) { case 0: if (isIETFLocaleTag && options && options.locale) { options.locale = options.locale.split('-')[0]; // Only use the first part of the locale 'en' for example } return [4 /*yield*/, this.getStripe()]; case 1: stripe = _a.sent(); return [2 /*return*/, stripe.elements(options)]; } }); }); }; /** * Create a payment request * NOTE: This is NOT supported for Firefox * @see https://stripe.com/docs/payment-request-api * * @param options - Payment information that should be used by Stripe * * @return the created request */ /** * Create a payment request * NOTE: This is NOT supported for Firefox * @see https://stripe.com/docs/payment-request-api * * @param {?} options - Payment information that should be used by Stripe * * @return {?} the created request */ StripeService.prototype.makePaymentRequest = /** * Create a payment request * NOTE: This is NOT supported for Firefox * @see https://stripe.com/docs/payment-request-api * * @param {?} options - Payment information that should be used by Stripe * * @return {?} the created request */ function (options) { return __awaiter(this, void 0, void 0, function () { var stripe; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getStripe()]; case 1: stripe = _a.sent(); return [2 /*return*/, stripe.paymentRequest(options)]; } }); }); }; /** * Creates a token from the given element * * @param element - The element from which the data needs to be extracted * @param [data] - an object containing additional payment information you might have collected * * @return A promise that resolves in a token or a rejection if the creation of the token failed */ /** * Creates a token from the given element * * @param {?} element - The element from which the data needs to be extracted * @param {?=} data * @return {?} A promise that resolves in a token or a rejection if the creation of the token failed */ StripeService.prototype.createTokenFromElement = /** * Creates a token from the given element * * @param {?} element - The element from which the data needs to be extracted * @param {?=} data * @return {?} A promise that resolves in a token or a rejection if the creation of the token failed */ function (element, data) { return __awaiter(this, void 0, void 0, function () { var stripe; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getStripe()]; case 1: stripe = _a.sent(); return [2 /*return*/, stripe.createToken(element, data).then(function (result) { if (result.error) { Promise.reject(result.error); } return result.token; })]; } }); }); }; /** * Creates a token from a bank account * * @param data - The data from the bank account that should be used for the token * * @return A promise that resolves in a token or a rejection if the creation of the token failed */ /** * Creates a token from a bank account * * @param {?} data - The data from the bank account that should be used for the token * * @return {?} A promise that resolves in a token or a rejection if the creation of the token failed */ StripeService.prototype.createTokenFromBankAccount = /** * Creates a token from a bank account * * @param {?} data - The data from the bank account that should be used for the token * * @return {?} A promise that resolves in a token or a rejection if the creation of the token failed */ function (data) { return __awaiter(this, void 0, void 0, function () { var stripe; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getStripe()]; case 1: stripe = _a.sent(); return [2 /*return*/, stripe.createToken('bank_account', data).then(function (result) { if (result.error) { Promise.reject(result.error); } return result.token; })]; } }); }); }; /** * Creates a token from the personal information of a customer * * @param data - The personal information that should be used for the creation of the token * * @return A promise that resolves in a token or a rejection if the creation of the token failed */ /** * Creates a token from the personal information of a customer * * @param {?} data - The personal information that should be used for the creation of the token * * @return {?} A promise that resolves in a token or a rejection if the creation of the token failed */ StripeService.prototype.createTokenFromPii = /** * Creates a token from the personal information of a customer * * @param {?} data - The personal information that should be used for the creation of the token * * @return {?} A promise that resolves in a token or a rejection if the creation of the token failed */ function (data) { return __awaiter(this, void 0, void 0, function () { var stripe; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getStripe()]; case 1: stripe = _a.sent(); return [2 /*return*/, stripe.createToken('pii', data).then(function (result) { if (result.error) { Promise.reject(result.error); } return result.token; })]; } }); }); }; /** * Creates a source object from the given element and data * * @param element - The element from which the data needs to be extracted * @param data - An object containing the type of Source you want to create and any additional payment source information * * @return A promise that resolves in a source object or a rejection if the creation of the source failed */ /** * Creates a source object from the given element and data * * @param {?} element - The element from which the data needs to be extracted * @param {?} data - An object containing the type of Source you want to create and any additional payment source information * * @return {?} A promise that resolves in a source object or a rejection if the creation of the source failed */ StripeService.prototype.createSourceFromElement = /** * Creates a source object from the given element and data * * @param {?} element - The element from which the data needs to be extracted * @param {?} data - An object containing the type of Source you want to create and any additional payment source information * * @return {?} A promise that resolves in a source object or a rejection if the creation of the source failed */ function (element, data) { return __awaiter(this, void 0, void 0, function () { var stripe; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getStripe()]; case 1: stripe = _a.sent(); return [2 /*return*/, stripe.createSource(element, data).then(function (result) { if (result.error) { Promise.reject(result.error); } return result.source; })]; } }); }); }; /** * Creates a source object from only data * * @param data - The data that should be used for the creation of the source object * * @return A promise that resolves in a source object or a rejection if the creation of the source failed */ /** * Creates a source object from only data * * @param {?} data - The data that should be used for the creation of the source object * * @return {?} A promise that resolves in a source object or a rejection if the creation of the source failed */ StripeService.prototype.createSourceFromData = /** * Creates a source object from only data * * @param {?} data - The data that should be used for the creation of the source object * * @return {?} A promise that resolves in a source object or a rejection if the creation of the source failed */ function (data) { return __awaiter(this, void 0, void 0, function () { var stripe; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getStripe()]; case 1: stripe = _a.sent(); return [2 /*return*/, stripe.createSource(data).then(function (result) { if (result.error) { Promise.reject(result.error); } return result.source; })]; } }); }); }; /** * Fetches an existing source based on the given parameters * * @param id - The unique identifier of the source * @param client_secret - A secret available to the web client that created the Source * * @return A promise that resolves in a source object or a rejection if the creation of the source failed */ /** * Fetches an existing source based on the given parameters * * @param {?} id - The unique identifier of the source * @param {?} client_secret - A secret available to the web client that created the Source * * @return {?} A promise that resolves in a source object or a rejection if the creation of the source failed */ StripeService.prototype.getSource = /** * Fetches an existing source based on the given parameters * * @param {?} id - The unique identifier of the source * @param {?} client_secret - A secret available to the web client that created the Source * * @return {?} A promise that resolves in a source object or a rejection if the creation of the source failed */ function (id, client_secret) { return __awaiter(this, void 0, void 0, function () { var stripe; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getStripe()]; case 1: stripe = _a.sent(); return [2 /*return*/, stripe.retrieveSource({ id: id, client_secret: client_secret }).then(function (result) { if (result.error) { Promise.reject(result.error); } return result.source; })]; } }); }); }; /** * Fetches the StripeJS instance * NOTE: Use the instance for token generation * * @return The StripeJS instance when it is available (since StripeJS is loaded Async) */ /** * Fetches the StripeJS instance * NOTE: Use the instance for token generation * * @return {?} The StripeJS instance when it is available (since StripeJS is loaded Async) */ StripeService.prototype.getStripe = /** * Fetches the StripeJS instance * NOTE: Use the instance for token generation * * @return {?} The StripeJS instance when it is available (since StripeJS is loaded Async) */ function () { return this.stripe$.pipe(filter(function (stripe) { return !!stripe; }), first()).toPromise().then(function (stripe) { return stripe; }); }; StripeService.decorators = [ { type: Injectable, args: [{ providedIn: 'root', },] } ]; StripeService.ctorParameters = function () { return [ { type: StripeLoader } ]; }; /** @nocollapse */ StripeService.ngInjectableDef = defineInjectable({ factory: function StripeService_Factory() { return new StripeService(inject(StripeLoader)); }, token: StripeService, providedIn: "root" }); return StripeService; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ export { NgxStripeModule, StripeService, StripeLoader, SupportedVersions, STRIPE_OPTIONS as ɵb, STRIPE_PUBLIC_KEY as ɵa, STRIPE_VERSION as ɵc }; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VjcmV0aHViLW5neC1zdHJpcGUuanMubWFwIiwic291cmNlcyI6WyJuZzovL0BzZWNyZXRodWIvbmd4LXN0cmlwZS9saWIvbW9kZWwvc3RyaXBlLmNvbnN0YW50LnRzIiwibmc6Ly9Ac2VjcmV0aHViL25neC1zdHJpcGUvbGliL21vZGVsL1N1cHBvcnRlZFZlcnNpb25zLnRzIiwibmc6Ly9Ac2VjcmV0aHViL25neC1zdHJpcGUvbGliL25neC1zdHJpcGUubW9kdWxlLnRzIiwibmc6Ly9Ac2VjcmV0aHViL25neC1zdHJpcGUvbGliL3NlcnZpY2VzL3N0cmlwZS1sb2FkZXIuc2VydmljZS50cyIsIm5nOi8vQHNlY3JldGh1Yi9uZ3gtc3RyaXBlL2xpYi9zZXJ2aWNlcy9zdHJpcGUuc2VydmljZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge0luamVjdGlvblRva2VufSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7U3VwcG9ydGVkVmVyc2lvbnN9IGZyb20gJy4vU3VwcG9ydGVkVmVyc2lvbnMnO1xuaW1wb3J0IHtTdHJpcGVDb25maWdPcHRpb25zfSBmcm9tICdzdHJpcGVqcyc7XG5cbi8qKlxuICogVGhlIHB1YmxpYyBrZXkgdGhhdCBzaG91bGQgYmUgdXNlZCBmb3IgY29ubmVjdGluZyB0byBTdHJpcGVcbiAqL1xuZXhwb3J0IGNvbnN0IFNUUklQRV9QVUJMSUNfS0VZID0gbmV3IEluamVjdGlvblRva2VuPHN0cmluZz4oJ1N0cmlwZSBwdWJsaWMga2V5Jyk7XG5cbi8qKlxuICogRXh0cmEgY29uZmlndXJhdGlvbiBvcHRpb25zIHRoYXQgY2FuIGJlIHVzZWQgdG8gZnVydGhlciBjb25maWd1cmUgU3RyaXBlXG4gKi9cbmV4cG9ydCBjb25zdCBTVFJJUEVfT1BUSU9OUyA9IG5ldyBJbmplY3Rpb25Ub2tlbjxTdHJpcGVDb25maWdPcHRpb25zPignT3B0aW9uYWwgY29uZmlndXJhdGlvbiBvcHRpb25zJyk7XG5cbi8qKlxuICogVGhlIHZlcnNpb24gb2Ygc3RyaXBlIHRoYXQgc2hvdWxkIGJlIHVzZWRcbiAqL1xuZXhwb3J0IGNvbnN0IFNUUklQRV9WRVJTSU9OID0gbmV3IEluamVjdGlvblRva2VuPFN1cHBvcnRlZFZlcnNpb25zPignU3RyaXBlIHZlcnNpb24gdG8gdXNlJyk7XG5cbi8qKlxuICogVGhlIGxvY2F0aW9uIG9mIHRoZSBzdHJpcGUgamF2YXNjcmlwdCBmaWxlXG4gKi9cbmV4cG9ydCBjb25zdCBTVFJJUEVfU0NSSVBUX0xPQ0FUSU9OID0gJ2h0dHBzOi8vanMuc3RyaXBlLmNvbS8nO1xuIiwiLyoqXG4gKiBBbGwgdGhlIFN0cmlwZSB2ZXJzaW9ucyB0aGF0IGFyZSBzdXBwb3J0ZWQgYnkgdGhpcyBsaWJyYXJ5XG4gKi9cbmV4cG9ydCBlbnVtIFN1cHBvcnRlZFZlcnNpb25zIHtcbiAgVjMgPSAndjMnLFxufVxuIiwiaW1wb3J0IHtNb2R1bGVXaXRoUHJvdmlkZXJzLCBOZ01vZHVsZX0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7U1RSSVBFX09QVElPTlMsIFNUUklQRV9QVUJMSUNfS0VZLCBTVFJJUEVfVkVSU0lPTn0gZnJvbSAnLi9tb2RlbC9zdHJpcGUuY29uc3RhbnQnO1xyXG5pbXBvcnQge1N1cHBvcnRlZFZlcnNpb25zfSBmcm9tICcuL21vZGVsL1N1cHBvcnRlZFZlcnNpb25zJztcclxuaW1wb3J0IHtTdHJpcGVDb25maWdPcHRpb25zfSBmcm9tICdzdHJpcGVqcyc7XHJcblxyXG5ATmdNb2R1bGUoe1xyXG4gIGltcG9ydHM6IFtdLFxyXG59KVxyXG5leHBvcnQgY2xhc3MgTmd4U3RyaXBlTW9kdWxlIHtcclxuXHJcbiAgLyoqXHJcbiAgICogQ3JlYXRlcyBhIG5ldyBpbnN0YW5jZSBvZiB0aGUgTmd4U3RyaXBlTW9kdWxlXHJcbiAgICogQHBhcmFtIGtleSAtIFRoZSBwdWJsaWMga2V5IHRoYXQgc2hvdWxkIGJlIHVzZWQgdG8gY29tbXVuaWNhdGUgd2l0aCBTdHJpcGVcclxuICAgKiBAcGFyYW0gb3B0aW9ucyAtIEFueSBvcHRpb25zIHRvIGNvbmZpZ3VyZSBTdHJpcGVKU1xyXG4gICAqIEBwYXJhbSBbdmVyc2lvbj1TdXBwb3J0ZWRWZXJzaW9ucy5WM10gLSBUaGUgdmVyc2lvbiBvZiBTdHJpcGUgdGhhdCBzaG91bGQgYmUgdXNlZFxyXG4gICAqL1xyXG4gIHB1YmxpYyBzdGF0aWMgZm9yUm9vdChrZXk6IHN0cmluZywgb3B0aW9ucz86IFN0cmlwZUNvbmZpZ09wdGlvbnMsIHZlcnNpb24gPSBTdXBwb3J0ZWRWZXJzaW9ucy5WMyk6IE1vZHVsZVdpdGhQcm92aWRlcnMge1xyXG4gICAgcmV0dXJuIHtcclxuICAgICAgbmdNb2R1bGU6IE5neFN0cmlwZU1vZHVsZSxcclxuICAgICAgcHJvdmlkZXJzOiBbXHJcbiAgICAgICAge3Byb3ZpZGU6IFNUUklQRV9QVUJMSUNfS0VZLCB1c2VWYWx1ZToga2V5fSxcclxuICAgICAgICB7cHJvdmlkZTogU1RSSVBFX09QVElPTlMsIHVzZVZhbHVlOiBvcHRpb25zfSxcclxuICAgICAgICB7cHJvdmlkZTogU1RSSVBFX1ZFUlNJT04sIHVzZVZhbHVlOiB2ZXJzaW9ufSxcclxuICAgICAgXSxcclxuICAgIH07XHJcbiAgfVxyXG59XHJcbiIsImltcG9ydCB7T2JzZXJ2YWJsZSwgU3Vic2NyaWJlcn0gZnJvbSAncnhqcyc7XG5pbXBvcnQge0luamVjdCwgSW5qZWN0YWJsZX0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQge1NUUklQRV9PUFRJT05TLCBTVFJJUEVfUFVCTElDX0tFWSwgU1RSSVBFX1NDUklQVF9MT0NBVElPTiwgU1RSSVBFX1ZFUlNJT059IGZyb20gJy4uL21vZGVsL3N0cmlwZS5jb25zdGFudCc7XG5pbXBvcnQge1N1cHBvcnRlZFZlcnNpb25zfSBmcm9tICcuLi9tb2RlbC9TdXBwb3J0ZWRWZXJzaW9ucyc7XG5pbXBvcnQge1N0cmlwZUNvbmZpZ09wdGlvbnMsIFN0cmlwZUpTfSBmcm9tICdzdHJpcGVqcyc7XG5cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnLFxufSlcbmV4cG9ydCBjbGFzcyBTdHJpcGVMb2FkZXIge1xuXG4gIC8qKlxuICAgKiBUaGUga2V5IHVuZGVyIHdoaWNoIHRoZSBzdHJpcGUgc2NyaXB0IGlzIHBsYWNlZCBpbiB0aGVcbiAgICogd2luZG93IG9iamVjdFxuICAgKlxuICAgKiBAZGVmYXVsdCAnU3RyaXBlJ1xuICAgKi9cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgU1RSSVBFX1dJTkRPV19LRVkgPSAnU3RyaXBlJztcblxuICBjb25zdHJ1Y3RvcihASW5qZWN0KFNUUklQRV9WRVJTSU9OKSBwcml2YXRlIHZlcnNpb246IFN1cHBvcnRlZFZlcnNpb25zLFxuICAgICAgICAgICAgICBASW5qZWN0KFNUUklQRV9PUFRJT05TKSBwcml2YXRlIG9wdGlvbnM6IFN0cmlwZUNvbmZpZ09wdGlvbnMsXG4gICAgICAgICAgICAgIEBJbmplY3QoU1RSSVBFX1BVQkxJQ19LRVkpIHByaXZhdGUga2V5OiBzdHJpbmcpIHtcbiAgfVxuXG4gIC8qKlxuICAgKiBGZXRjaGVzIHRoZSBzdHJpcGUgaW5zdGFuY2UgZnJvbSB0aGUgRE9NXG4gICAqXG4gICAqIEByZXR1cm4gVGhlIFN0cmlwZSBpbnN0YW5jZSBvciBudWxsIGlmIGl0IGlzIG5vdCB5ZXQgcmVnaXN0ZXJlZFxuICAgKi9cbiAgcHVibGljIHN0YXRpYyBnZXRTdHJpcGVJbnN0YW5jZShrZXk6IHN0cmluZywgb3B0aW9uczogU3RyaXBlQ29uZmlnT3B0aW9ucyk6IFN0cmlwZUpTIHwgbnVsbCB7XG4gICAgY29uc3Qgc3RyaXBlSW5zdGFuY2UgPSB3aW5kb3dbU3RyaXBlTG9hZGVyLlNUUklQRV9XSU5ET1dfS0VZXTtcbiAgICBpZiAoc3RyaXBlSW5zdGFuY2UpIHtcbiAgICAgIHJldHVybiBzdHJpcGVJbnN0YW5jZShrZXksIG9wdGlvbnMpO1xuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVja3Mgd2hldGhlciBvciBub3QgU3RyaXBlSlMgaGFzIGJlZW4gbG9hZGVkXG4gICAqL1xuICBwdWJsaWMgc3RhdGljIGlzU3RyaXBlTG9hZGVkKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAhIXdpbmRvd1tTdHJpcGVMb2FkZXIuU1RSSVBFX1dJTkRPV19LRVldO1xuICB9XG5cbiAgLyoqXG4gICAqIEluamVjdHMgdGhlIHNjcmlwdCB0YWcgaW50byB0aGUgYm9keSBvZiB0aGUgcGFnZSBpbiBvcmRlciB0byBsYXp5XG4gICAqIGxvYWQgdGhlIFN0cmlwZSBzY3JpcHRcbiAgICpcbiAgICogQHJldHVybiBPYnNlcnZhYmxlIHRvIGluZGljYXRlIHdoZW4gdGhlIHNjcmlwdCBoYXMgZmluaXNoZWQgbG9hZGluZ1xuICAgKi9cbiAgcHVibGljIGxvYWRTY3JpcHQoKTogT2JzZXJ2YWJsZTxTdHJpcGVKUz4ge1xuICAgIHJldHVybiBuZXcgT2JzZXJ2YWJsZTxTdHJpcGVKUz4oKG9ic2VydmVyOiBTdWJzY3JpYmVyPFN0cmlwZUpTPikgPT4ge1xuICAgICAgaWYgKCFTdHJpcGVMb2FkZXIuaXNTdHJpcGVMb2FkZWQoKSkge1xuICAgICAgICBjb25zdCBzY3JpcHQgPSBTdHJpcGVMb2FkZXIuY29uc3RydWN0U2NyaXB0KHRoaXMuZ2V0U3RyaXBlU2NyaXB0VXJsKCkpO1xuXG4gICAgICAgIHNjcmlwdC5vbmxvYWQgPSAoKCkgPT4ge1xuICAgICAgICAgIG9ic2VydmVyLm5leHQoU3RyaXBlTG9hZGVyLmdldFN0cmlwZUluc3RhbmNlKHRoaXMua2V5LCB0aGlzLm9wdGlvbnMpKTtcbiAgICAgICAgICBvYnNlcnZlci5jb21wbGV0ZSgpO1xuICAgICAgICB9KTtcblxuICAgICAgICBzY3JpcHQub25lcnJvciA9ICgpID0+IHtcbiAgICAgICAgICBvYnNlcnZlci5lcnJvcignRmFpbGVkIHRvIGxvYWQgdGhlIFN0cmlwZSBzY3JpcHQhJyk7XG4gICAgICAgICAgb2JzZXJ2ZXIuY29tcGxldGUoKTtcbiAgICAgICAgfTtcblxuICAgICAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHNjcmlwdCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBvYnNlcnZlci5uZXh0KFN0cmlwZUxvYWRlci5nZXRTdHJpcGVJbnN0YW5jZSh0aGlzLmtleSwgdGhpcy5vcHRpb25zKSk7XG4gICAgICAgIG9ic2VydmVyLmNvbXBsZXRlKCk7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogQ29uc3RydWN0cyB0aGUgdXJsIHRoYXQgc2hvdWxkIGJlIHVzZWQgZm9yIGxvYWRpbmcgdGhlIFN0cmlwZSBzY3JpcHRcbiAgICogQHJldHVybiBVUkwgdG8gc3RyaXBlIHNjcmlwdCBsb2NhdGlvblxuICAgKi9cbiAgcHJpdmF0ZSBnZXRTdHJpcGVTY3JpcHRVcmwoKTogc3RyaW5nIHtcbiAgICByZXR1cm4gU1RSSVBFX1NDUklQVF9MT0NBVElPTiArIHRoaXMudmVyc2lvbiArICcvJztcbiAgfVxuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RzIGEgc2NyaXB0IGVsZW1lbnQgdGhhdCBsb2FkcyBqYXZhc2NyaXB0IGZyb20gdGhlIGdpdmVuIHVybFxuICAgKiBAcGFyYW0gdXJsIC0gVGhlIFVSTCBmcm9tIHdoaWNoIHRoZSBqYXZhc2NyaXB0IHNob3VsZCBiZSBsb2FkZWRcbiAgICpcbiAgICogQHJldHVybiBBIHNjcmlwdCBlbGVtZW50IHRoYXQgY2FuIGJlIGF0dGFjaGVkIHRvIHRoZSBET01cbiAgICovXG4gIHByaXZhdGUgc3RhdGljIGNvbnN0cnVjdFNjcmlwdCh1cmw6IHN0cmluZyk6IEhUTUxTY3JpcHRFbGVtZW50IHtcbiAgICBjb25zdCBzY3JpcHQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtcbiAgICBzY3JpcHQudHlwZSA9ICd0ZXh0L2phdmFzY3JpcHQnO1xuICAgIHNjcmlwdC5hc3luYyA9IHRydWU7XG4gICAgc2NyaXB0LmRlZmVyID0gdHJ1ZTtcbiAgICBzY3JpcHQuc3JjID0gdXJsO1xuXG4gICAgcmV0dXJuIHNjcmlwdDtcbiAgfVxufVxuIiwiaW1wb3J0IHtJbmplY3RhYmxlfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHtTdHJpcGVKUywgU3RyaXBlQ29uZmlnT3B0aW9uc30gZnJvbSAnc3RyaXBlanMnO1xyXG5pbXBvcnQge1N0cmlwZUVsZW1lbnQsIEVsZW1lbnRGYWN0b3J5LCBFbGVtZW50Q3JlYXRvck9wdGlvbnN9IGZyb20gJ3N0cmlwZWpzL2VsZW1lbnQnO1xyXG5pbXBvcnQge1N0cmlwZVBheW1lbnRPcHRpb25zLCBTdHJpcGVQYXltZW50UmVxdWVzdH0gZnJvbSAnc3RyaXBlanMvcGF5bWVudCc7XHJcbmltcG9ydCB7QmFua1Rva2VuRGF0YSwgSUJBTlRva2VuRGF0YSwgUGlpVG9rZW5EYXRhLCBUb2tlbiwgVG9rZW5EYXRhLCBUb2tlblJlc3VsdH0gZnJvbSAnc3RyaXBlanMvdG9rZW4nO1xyXG5pbXBvcnQge1NvdXJjZSwgU291cmNlRGF0YSwgU291cmNlUmVzdWx0fSBmcm9tICdzdHJpcGVqcy9zb3VyY2UnO1xyXG5pbXBvcnQge0JlaGF2aW9yU3ViamVjdCwgT2JzZXJ2YWJsZX0gZnJvbSAncnhqcyc7XHJcbmltcG9ydCB7ZmlsdGVyLCBmaXJzdH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xyXG5pbXBvcnQge1N0cmlwZUxvYWRlcn0gZnJvbSAnLi9zdHJpcGUtbG9hZGVyLnNlcnZpY2UnO1xyXG5cclxuQEluamVjdGFibGUoe1xyXG4gIHByb3ZpZGVkSW46ICdyb290JyxcclxufSlcclxuZXhwb3J0IGNsYXNzIFN0cmlwZVNlcnZpY2Uge1xyXG4gIC8qKlxyXG4gICAqIEEgQmVoYXZpb3JTdWJqZWN0IGNvbnRhaW5pbmcgdGhlIFN0cmlwZUpTIG9iamVjdFxyXG4gICAqXHJcbiAgICogU2luY2UgdGhlIHNjcmlwdCBpcyBsb2FkZWQgQXN5bmMgd2UgbmVlZCBhbiBvcHRpb25zIGZvciBhbGwgb3VyIGZ1bmN0aW9uc1xyXG4gICAqIHRvIHdhaXQgZm9yIHN0cmlwZSB0byBoYXZlIGJlZW4gbG9hZGVkXHJcbiAgICovXHJcbiAgcHJpdmF0ZSBzdHJpcGUkID0gbmV3IEJlaGF2aW9yU3ViamVjdDxTdHJpcGVKUyB8IG51bGw+KG51bGwpO1xyXG5cclxuICAvKipcclxuICAgKiBMYXp5IGxvYWQgdGhlIFN0cmlwZUpTIGphdmFzY3JpcHQgZmlsZSBvbiBmaXJzdCB1c2FnZSBvZiB0aGUgc2VydmljZVxyXG4gICAqIEBwYXJhbSBsb2FkZXIgLSBUaGUgbG9hZGVyIHRoYXQgc2hvdWxkIGJlIHVzZWQgZm9yIGxvYWRpbmcgU3RyaXBlSlNcclxuICAgKi9cclxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIGxvYWRlcjogU3RyaXBlTG9hZGVyKSB7XHJcbiAgICBsb2FkZXIubG9hZFNjcmlwdCgpXHJcbiAgICAgIC5zdWJzY3JpYmUoXHJcbiAgICAgICAgKHN0cmlwZTogU3RyaXBlSlMpID0+IHRoaXMuc3RyaXBlJC5uZXh0KHN0cmlwZSksXHJcbiAgICAgICAgKCkgPT4ge1xyXG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTdHJpcGUgY291bGQgbm90IGJlIGxvYWRlZCEnKTtcclxuICAgICAgICB9LFxyXG4gICAgICApO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogQ3JlYXRlcyBhIG5ldyBzdHJpcGUgaW5zdGFuY2Ugd2l0aCB0aGUgZ2l2ZW4ga2V5XHJcbiAgICogQHBhcmFtIGtleSAtIFRoZSBwdWJsaWMga2V5IHRoYXQgc2hvdWxkIGJlIHVzZWQgdG8gY29tbXVuaWNhdGUgd2l0aCBTdHJpcGVcclxuICAgKiBAcGFyYW0gb3B0aW9ucyAtIEFueSBvcHRpb25zIHRvIGNvbmZpZ3VyZSBTdHJpcGVKU1xyXG4gICAqL1xyXG4gIHB1YmxpYyBjaGFuZ2VLZXkoa2V5OiBzdHJpbmcsIG9wdGlvbnM/OiBTdHJpcGVDb25maWdPcHRpb25zKTogdm9pZCB7XHJcbiAgICB0aGlzLnN0cmlwZSQubmV4dChTdHJpcGVMb2FkZXIuZ2V0U3RyaXBlSW5zdGFuY2Uoa2V5LCBvcHRpb25zKSk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBDb25maWd1cmVzIHRoZSBgRWxlbWVudHNgIG9iamVjdCBmcm9tIFN0cmlwZUpTIHdpdGggdGhlIGdpdmVuIG9wdGlvbnNcclxuICAgKiBAc2VlIGh0dHBzOi8vc3RyaXBlLmNvbS9kb2NzL3N0cmlwZS1qcy9lbGVtZW50cy9xdWlja3N0YXJ0I2NyZWF0ZS1mb3JtXHJcbiAgICogQHBhcmFtIFtvcHRpb25zXSAtIEFueSBjb25maWd1cmF0aW9uIG9wdGlvbnMgZm9yIHRoZSBFbGVtZW50cyBvYmplY3RcclxuICAgKiBAcGFyYW0gW2lzSUVURkxvY2FsZVRhZ10gLSBXaGV0aGVyIG9yIG5vdCB0aGUgb3B0aW9ucyBgbG9jYWxlYCBpcyBmb3JtYXR0ZWQgYXMgYSBJRVRGTG9jYWxlVGFnLlxyXG4gICAqIElmIHRydWUgdGhlIGxvY2FsZSB3aWxsIGJlIGZvcm1hdHRlZCBieSB0aGlzIGZ1bmN0aW9uXHJcbiAgICpcclxuICAgKiBAcmV0dXJuIE9ic2VydmFibGUgdGhhdCByZXNvbHZlcyBpbiBhbiBTdHJpcGVKUyBFbGVtZW50c0NyZWF0b3JcclxuICAgKi9cclxuICBwdWJsaWMgYXN5bmMgZ2V0RWxlbWVudEZhY3Rvcnkob3B0aW9ucz86IEVsZW1lbnRDcmVhdG9yT3B0aW9ucywgaXNJRVRGTG9jYWxlVGFnID0gZmFsc2UpOiBQcm9taXNlPEVsZW1lbnRGYWN0b3J5PiB7XHJcbiAgICBpZiAoaXNJRVRGTG9jYWxlVGFnICYmIG9wdGlvbnMgJiYgb3B0aW9ucy5sb2NhbGUpIHtcclxuICAgICAgb3B0aW9ucy5sb2NhbGUgPSBvcHRpb25zLmxvY2FsZS5zcGxpdCgnLScpWzBdOyAvLyBPbmx5IHVzZSB0aGUgZmlyc3QgcGFydCBvZiB0aGUgbG9jYWxlICdlbicgZm9yIGV4YW1wbGVcclxuICAgIH1cclxuXHJcbiAgICBjb25zdCBzdHJpcGUgPSBhd2FpdCB0aGlzLmdldFN0cmlwZSgpO1xyXG4gICAgcmV0dXJuIHN0cmlwZS5lbGVtZW50cyhvcHRpb25zKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIENyZWF0ZSBhIHBheW1lbnQgcmVxdWVzdFxyXG4gICAqIE5PVEU6IFRoaXMgaXMgTk9UIHN1cHBvcnRlZCBmb3IgRmlyZWZveFxyXG4gICAqIEBzZWUgaHR0cHM6Ly9zdHJpcGUuY29tL2RvY3MvcGF5bWVudC1yZXF1ZXN0LWFwaVxyXG4gICAqXHJcbiAgICogQHBhcmFtIG9wdGlvbnMgLSBQYXltZW50IGluZm9ybWF0aW9uIHRoYXQgc2hvdWxkIGJlIHVzZWQgYnkgU3RyaXBlXHJcbiAgICpcclxuICAgKiBAcmV0dXJuIHRoZSBjcmVhdGVkIHJlcXVlc3RcclxuICAgKi9cclxuICBwdWJsaWMgYXN5bmMgbWFrZVBheW1lbnRSZXF1ZXN0KG9wdGlvbnM6IFN0cmlwZVBheW1lbnRPcHRpb25zKTogUHJvbWlzZTxTdHJpcGVQYXltZW50UmVxdWVzdD4ge1xyXG4gICAgY29uc3Qgc3RyaXBlID0gYXdhaXQgdGhpcy5nZXRTdHJpcGUoKTtcclxuICAgIHJldHVybiBzdHJpcGUucGF5bWVudFJlcXVlc3Qob3B0aW9ucyk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBDcmVhdGVzIGEgdG9rZW4gZnJvbSB0aGUgZ2l2ZW4gZWxlbWVudFxyXG4gICAqXHJcbiAgICogQHBhcmFtIGVsZW1lbnQgLSBUaGUgZWxlbWVudCBmcm9tIHdoaWNoIHRoZSBkYXRhIG5lZWRzIHRvIGJlIGV4dHJhY3RlZFxyXG4gICAqIEBwYXJhbSBbZGF0YV0gLSBhbiBvYmplY3QgY29udGFpbmluZyBhZGRpdGlvbmFsIHBheW1lbnQgaW5mb3JtYXRpb24geW91IG1pZ2h0IGhhdmUgY29sbGVjdGVkXHJcbiAgICpcclxuICAgKiBAcmV0dXJuIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIGluIGEgdG9rZW4gb3IgYSByZWplY3Rpb24gaWYgdGhlIGNyZWF0aW9uIG9mIHRoZSB0b2tlbiBmYWlsZWRcclxuICAgKi9cclxuICBwdWJsaWMgYXN5bmMgY3JlYXRlVG9rZW5Gcm9tRWxlbWVudChlbGVtZW50OiBTdHJpcGVFbGVtZW50LCBkYXRhPzogVG9rZW5EYXRhIHwgSUJBTlRva2VuRGF0YSk6IFByb21pc2U8VG9rZW4+IHtcclxuICAgIGNvbnN0IHN0cmlwZSA9IGF3YWl0IHRoaXMuZ2V0U3RyaXBlKCk7XHJcbiAgICByZXR1cm4gc3RyaXBlLmNyZWF0ZVRva2VuKGVsZW1lbnQsIGRhdGEpLnRoZW4oKHJlc3VsdDogVG9rZW5SZXN1bHQpID0+IHtcclxuICAgICAgaWYgKHJlc3VsdC5lcnJvcikge1xyXG4gICAgICAgIFByb21pc2UucmVqZWN0KHJlc3VsdC5lcnJvcik7XHJcbiAgICAgIH1cclxuICAgICAgcmV0dXJuIHJlc3VsdC50b2tlbjtcclxuICAgIH0pO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogQ3JlYXRlcyBhIHRva2VuIGZyb20gYSBiYW5rIGFjY291bnRcclxuICAgKlxyXG4gICAqIEBwYXJhbSBkYXRhIC0gVGhlIGRhdGEgZnJvbSB0aGUgYmFuayBhY2NvdW50IHRoYXQgc2hvdWxkIGJlIHVzZWQgZm9yIHRoZSB0b2tlblxyXG4gICAqXHJcbiAgICogQHJldHVybiBBIHByb21pc2UgdGhhdCByZXNvbHZlcyBpbiBhIHRva2VuIG9yIGEgcmVqZWN0aW9uIGlmIHRoZSBjcmVhdGlvbiBvZiB0aGUgdG9rZW4gZmFpbGVkXHJcbiAgICovXHJcbiAgcHVibGljIGFzeW5jIGNyZWF0ZVRva2VuRnJvbUJhbmtBY2NvdW50KGRhdGE6IEJhbmtUb2tlbkRhdGEpOiBQcm9taXNlPFRva2VuPiB7XHJcbiAgICBjb25zdCBzdHJpcGUgPSBhd2FpdCB0aGlzLmdldFN0cmlwZSgpO1xyXG4gICAgcmV0dXJuIHN0cmlwZS5jcmVhdGVUb2tlbignYmFua19hY2NvdW50JywgZGF0YSkudGhlbigocmVzdWx0OiBUb2tlblJlc3VsdCkgPT4ge1xyXG4gICAgICBpZiAocmVzdWx0LmVycm9yKSB7XHJcbiAgICAgICAgUHJvbWlzZS5yZWplY3QocmVzdWx0LmVycm9yKTtcclxuICAgICAgfVxyXG4gICAgICByZXR1cm4gcmVzdWx0LnRva2VuO1xyXG4gICAgfSk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBDcmVhdGVzIGEgdG9rZW4gZnJvbSB0aGUgcGVyc29uYWwgaW5mb3JtYXRpb24gb2YgYSBjdXN0b21lclxyXG4gICAqXHJcbiAgICogQHBhcmFtIGRhdGEgLSBUaGUgcGVyc29uYWwgaW5mb3JtYXRpb24gdGhhdCBzaG91bGQgYmUgdXNlZCBmb3IgdGhlIGNyZWF0aW9uIG9mIHRoZSB0b2tlblxyXG4gICAqXHJcbiAgICogQHJldHVybiBBIHByb21pc2UgdGhhdCByZXNvbHZlcyBpbiBhIHRva2VuIG9yIGEgcmVqZWN0aW9uIGlmIHRoZSBjcmVhdGlvbiBvZiB0aGUgdG9rZW4gZmFpbGVkXHJcbiAgICovXHJcbiAgcHVibGljIGFzeW5jIGNyZWF0ZVRva2VuRnJvbVBpaShkYXRhOiBQaWlUb2tlbkRhdGEpOiBQcm9taXNlPFRva2VuPiB7XHJcbiAgICBjb25zdCBzdHJpcGUgPSBhd2FpdCB0aGlzLmdldFN0cmlwZSgpO1xyXG4gICAgcmV0dXJuIHN0cmlwZS5jcmVhdGVUb2tlbigncGlpJywgZGF0YSkudGhlbigocmVzdWx0OiBUb2tlblJlc3VsdCkgPT4ge1xyXG4gICAgICBpZiAocmVzdWx0LmVycm9yKSB7XHJcbiAgICAgICAgUHJvbWlzZS5yZWplY3QocmVzdWx0LmVycm9yKTtcclxuICAgICAgfVxyXG4gICAgICByZXR1cm4gcmVzdWx0LnRva2VuO1xyXG4gICAgfSk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBDcmVhdGVzIGEgc291cmNlIG9iamVjdCBmcm9tIHRoZSBnaXZlbiBlbGVtZW50IGFuZCBkYXRhXHJcbiAgICpcclxuICAgKiBAcGFyYW0gZWxlbWVudCAtIFRoZSBlbGVtZW50IGZyb20gd2hpY2ggdGhlIGRhdGEgbmVlZHMgdG8gYmUgZXh0cmFjdGVkXHJcbiAgICogQHBhcmFtIGRhdGEgLSBBbiBvYmplY3QgY29udGFpbmluZyB0aGUgdHlwZSBvZiBTb3VyY2UgeW91IHdhbnQgdG8gY3JlYXRlIGFuZCBhbnkgYWRkaXRpb25hbCBwYXltZW50IHNvdXJjZSBpbmZvcm1hdGlvblxyXG4gICAqXHJcbiAgICogQHJldHVybiBBIHByb21pc2UgdGhhdCByZXNvbHZlcyBpbiBhIHNvdXJjZSBvYmplY3Qgb3IgYSByZWplY3Rpb24gaWYgdGhlIGNyZWF0aW9uIG9mIHRoZSBzb3VyY2UgZmFpbGVkXHJcbiAgICovXHJcbiAgcHVibGljIGFzeW5jIGNyZWF0ZVNvdXJjZUZyb21FbGVtZW50KGVsZW1lbnQ6IFN0cmlwZUVsZW1lbnQsIGRhdGE6IFNvdXJjZURhdGEpOiBQcm9taXNlPFNvdXJjZT4ge1xyXG4gICAgY29uc3Qgc3RyaXBlID0gYXdhaXQgdGhpcy5nZXRTdHJpcGUoKTtcclxuICAgIHJldHVybiBzdHJpcGUuY3JlYXRlU291cmNlKGVsZW1lbnQsIGRhdGEpLnRoZW4oKHJlc3VsdDogU291cmNlUmVzdWx0KSA9PiB7XHJcbiAgICAgIGlmIChyZXN1bHQuZXJyb3IpIHtcclxuICAgICAgICBQcm9taXNlLnJlamVjdChyZXN1bHQuZXJyb3IpO1xyXG4gICAgICB9XHJcbiAgICAgIHJldHVybiByZXN1bHQuc291cmNlO1xyXG4gICAgfSk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBDcmVhdGVzIGEgc291cmNlIG9iamVjdCBmcm9tIG9ubHkgZGF0YVxyXG4gICAqXHJcbiAgICogQHBhcmFtIGRhdGEgLSBUaGUgZGF0YSB0aGF0IHNob3VsZCBiZSB1c2VkIGZvciB0aGUgY3JlYXRpb24gb2YgdGhlIHNvdXJjZSBvYmplY3RcclxuICAgKlxyXG4gICAqIEByZXR1cm4gQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgaW4gYSBzb3VyY2Ugb2JqZWN0IG9yIGEgcmVqZWN0aW9uIGlmIHRoZSBjcmVhdGlvbiBvZiB0aGUgc291cmNlIGZhaWxlZFxyXG4gICAqL1xyXG4gIHB1YmxpYyBhc3luYyBjcmVhdGVTb3VyY2VGcm9tRGF0YShkYXRhOiBTb3VyY2VEYXRhKTogUHJvbWlzZTxTb3VyY2U+IHtcclxuICAgIGNvbnN0IHN0cmlwZSA9IGF3YWl0IHRoaXMuZ2V0U3RyaXBlKCk7XHJcbiAgICByZXR1cm4gc3RyaXBlLmNyZWF0ZVNvdXJjZShkYXRhKS50aGVuKChyZXN1bHQ6IFNvdXJjZVJlc3VsdCkgPT4ge1xyXG4gICAgICBpZiAocmVzdWx0LmVycm9yKSB7XHJcbiAgICAgICAgUHJvbWlzZS5yZWplY3QocmVzdWx0LmVycm9yKTtcclxuICAgICAgfVxyXG4gICAgICByZXR1cm4gcmVzdWx0LnNvdXJjZTtcclxuICAgIH0pO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogRmV0Y2hlcyBhbiBleGlzdGluZyBzb3VyY2UgYmFzZWQgb24gdGhlIGdpdmVuIHBhcmFtZXRlcnNcclxuICAgKlxyXG4gICAqIEBwYXJhbSBpZCAtIFRoZSB1bmlxdWUgaWRlbnRpZmllciBvZiB0aGUgc291cmNlXHJcbiAgICogQHBhcmFtIGNsaWVudF9zZWNyZXQgLSBBIHNlY3JldCBhdmFpbGFibGUgdG8gdGhlIHdlYiBjbGllbnQgdGhhdCBjcmVhdGVkIHRoZSBTb3VyY2VcclxuICAgKlxyXG4gICAqIEByZXR1cm4gQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgaW4gYSBzb3VyY2Ugb2JqZWN0IG9yIGEgcmVqZWN0aW9uIGlmIHRoZSBjcmVhdGlvbiBvZiB0aGUgc291cmNlIGZhaWxlZFxyXG4gICAqL1xyXG4gIHB1YmxpYyBhc3luYyBnZXRTb3VyY2UoaWQ6IHN0cmluZywgY2xpZW50X3NlY3JldDogc3RyaW5nKTogUHJvbWlzZTxTb3VyY2U+IHtcclxuICAgIGNvbnN0IHN0cmlwZSA9IGF3YWl0IHRoaXMuZ2V0U3RyaXBlKCk7XHJcbiAgICByZXR1cm4gc3RyaXBlLnJldHJpZXZlU291cmNlKHtpZCwgY2xpZW50X3NlY3JldH0pLnRoZW4oKHJlc3VsdDogU291cmNlUmVzdWx0KSA9PiB7XHJcbiAgICAgIGlmIChyZXN1bHQuZXJyb3IpIHtcclxuICAgICAgICBQcm9taXNlLnJlamVjdChyZXN1bHQuZXJyb3IpO1xyXG4gICAgICB9XHJcbiAgICAgIHJldHVybiByZXN1bHQuc291cmNlO1xyXG4gICAgfSk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBGZXRjaGVzIHRoZSBTdHJpcGVKUyBpbnN0YW5jZVxyXG4gICAqIE5PVEU6IFVzZSB0aGUgaW5zdGFuY2UgZm9yIHRva2VuIGdlbmVyYXRpb25cclxuICAgKlxyXG4gICAqIEByZXR1cm4gVGhlIFN0cmlwZUpTIGluc3RhbmNlIHdoZW4gaXQgaXMgYXZhaWxhYmxlIChzaW5jZSBTdHJpcGVKUyBpcyBsb2FkZWQgQXN5bmMpXHJcbiAgICovXHJcbiAgcHJpdmF0ZSBnZXRTdHJpcGUoKTogUHJvbWlzZTxTdHJpcGVKUz4ge1xyXG4gICAgcmV0dXJuIHRoaXMuc3RyaXBlJC5waXBlKFxyXG4gICAgICBmaWx0ZXIoKHN0cmlwZTogU3RyaXBlSlMgfCBudWxsKSA9PiAhIXN0cmlwZSksXHJcbiAgICAgIGZpcnN0KCksXHJcbiAgICApLnRvUHJvbWlzZSgpLnRoZW4oKHN0cmlwZTogU3RyaXBlSlMpID0+IHN0cmlwZSk7XHJcbiAgfVxyXG59XHJcbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBQTs7OztBQU9BLElBQWEsaUJBQWlCLEdBQUcsSUFBSSxjQUFjLENBQVMsbUJBQW1CLENBQUM7Ozs7O0FBS2hGLElBQWEsY0FBYyxHQUFHLElBQUksY0FBYyxDQUFzQixnQ0FBZ0MsQ0FBQzs7Ozs7QUFLdkcsSUFBYSxjQUFjLEdBQUcsSUFBSSxjQUFjLENBQW9CLHVCQUF1QixDQUFDOzs7OztBQUs1RixJQUFhLHNCQUFzQixHQUFHLHdCQUF3Qjs7Ozs7Ozs7SUNsQjVELElBQUssSUFBSTs7Ozs7OztBQ0pYO0lBS0E7S0FxQkM7Ozs7Ozs7Ozs7Ozs7O0lBVmUsdUJBQU87Ozs7Ozs7SUFBckIsVUFBc0IsR0FBVyxFQUFFLE9BQTZCLEVBQUUsT0FBOEI7UUFBOUIsd0JBQUEsRUFBQSxV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