UNPKG

relution-sdk

Version:

Relution Software Development Kit for TypeScript and JavaScript

546 lines 81.6 kB
/* * @file web/http.ts * Relution SDK * * Created by Thomas Beckmann on 28.04.2016 * Copyright 2016 M-Way Solutions GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @module web */ /** */ "use strict"; var Q = require('q'); var _ = require('lodash'); var auth = require('../security/auth'); var diag = require('../core/diag'); var http = require('http'); var init = require('../core/init'); var offline = require('./offline'); var request = require('request'); var server = require('../security/server'); var urls = require('./urls'); // require request.js to manage cookies for us var requestDefaults = { json: true, jar: true, withCredentials: true }; var requestWithDefaults = request.defaults(requestDefaults); /** * drives an HTTP request against the Relution server. * * Behavior of this method is simplified from most HTTP/AJAX implementations: * - When the HTTP request succeeds the resulting promise resolves to the response body. * - In case of a network Error the promise resolves to an HttpError object providing `requestUrl` * but neither `statusCode` nor `statusMessage`. * - In case of HTTP failure the resulting promise is rejected to an HttpError-like object carrying * the properties `requestUrl`, `statusCode` and `statusMessage`. * - If the server responds a JSON, it is parsed and assumed to be an HttpError-like object. The * object is augmented by the properties as defined above. * - Otherwise the body is stored as `message` of an HttpError object created. Again, the properties * above are provided. * - Finally, in case of HTTP failure with the server not providing any response body, the HttpError * `message` is set to the `statusMessage`. * * Thus, to differentiate network failures from server-side failures the `statusCode` of the * HttpError rejection is to being used. For deeper inspection provide an * [[options.responseCallback]]. * * ```javascript * Relution.init({ * serverUrl: 'http://localhost:8080', * organization: 'myOrga' * }); * * let httpOptions: HttpOptions = {method: 'GET', url: 'api/v1/posts'}; * * //usage as Promise * Relution.web.ajax(httpOptions) * .then((resp) => console.log('posts', resp);) * .catch((e:Relution.web.HttpError) => console.error(e.message, e)) * .finally(() => console.log('loading complete!')); * * // as Observable * Observable.fromPromise(Relution.web.ajax(httpOptions)).subscribe( * (resp: any) => console.log('posts', resp), * (e:Relution.web.HttpError) => console.error(e.message, e);, * () => console.log('loading complete!') * ) * ``` * @param options of request, including target `url`. * @return {Q.Promise} of response body, in case of failure rejects to an HttpError object * including `requestUrl`, `statusCode` and `statusMessage`. */ function ajax(options) { var serverUrl = urls.resolveServer(options.url, options); var serverObj = server.Server.getInstance(serverUrl); if (!serverObj.sessionUserUuid && serverObj.credentials) { // not logged in var credentials_1 = serverObj.credentials; serverObj.credentials = null; return login(credentials_1, { serverUrl: serverUrl }).then(function () { diag.debug.assert(function () { return !!serverObj.sessionUserUuid; }); diag.debug.assert(function () { return serverObj.credentials == credentials_1; }); return ajax(options); // repeat after login }); } // process options var currentOptions = serverObj.applyOptions({ serverUrl: serverUrl, agentOptions: options.agentOptions || init.initOptions.agentOptions, agentClass: options.agentClass || init.initOptions.agentClass, // options taking effect at request time application: options.application || init.initOptions.application, tenantOrga: options.tenantOrga || init.initOptions.tenantOrga }); // resolve target url var url = urls.resolveUrl(options.url, currentOptions); diag.debug.debug(options.method + ' ' + url); diag.debug.assert(function () { return url.substr(0, serverUrl.length) === serverUrl; }); var requestCallback = options.requestCallback || _.identity; var responseCallback = options.responseCallback || _.identity; options = _.clone(options); options.agentOptions = currentOptions.agentOptions; options.agentClass = currentOptions.agentClass; if (currentOptions.clientCertificate) { // apply certificate options (must copy options.agentOptions) options.agentOptions = _.defaults({}, currentOptions.clientCertificate, options.agentOptions); } var headers = {}; if (serverObj.sessionUserUuid) { // add X-Gofer-User header so that server may check we are running under correct identity headers['X-Gofer-User'] = serverObj.sessionUserUuid; } if (currentOptions.clientApp) { // add X-Relution-ClientApp for server-side analytics headers['X-Relution-ClientApp'] = currentOptions.clientApp; } if (!_.isEmpty(headers)) { options.headers = _.defaults(headers, options.headers); } return Q.Promise(function (resolveResult, rejectResult) { var promiseResponse = Q.Promise(function (resolveResponse, rejectResponse) { var resp; var req; try { req = requestWithDefaults(url, options, function (error, response, body) { if (response === void 0) { response = resp; } // node.js assigns status string as body for status codes not having body data if (response && response.statusCode === 202) { diag.debug.assert(body === http.STATUS_CODES[202], body); body = undefined; // resolves promise to undefined below } // error processing if (!error && response && response.statusCode >= 400) { if (_.isError(body)) { // correct but practically impossible error = body; } else if (_.isString(body)) { // use plain-text as Error message error = new Error(body); } else if (_.isObjectLike(body)) { // body is object representation of server-side error or exception, // converting to true Error object here error = new Error(response.statusMessage); diag.debug.assert(function () { return !_.isArray(body); }, 'kicks in for array responses as well, not sure if this is desirable'); _.extend(error, body); } else { // handles numbers, booleans, etc. assigning as cause of failure error = new Error(response.statusMessage); if (!_.isUndefined(body)) { error.cause = body; } } } if (error) { // completes HttpError construction diag.debug.assertIsError(error, 'should operate true Error instances'); error.requestUrl = url; if (response) { error.statusCode = response.statusCode; error.statusMessage = response.statusMessage; Object.defineProperty(error, 'rawResponse', { value: response, enumerable: false }); } Object.defineProperty(error, 'rawRequest', { value: req, enumerable: false }); } if (!response) { // network connectivity problem serverObj.serverInfos = null; diag.debug.assertIsError(error); if (promiseResponse) { rejectResponse(error); // will also rejectResult(error) } else { rejectResult(error); // promiseResponse not constructed yet } } else { // server information serverObj.serverInfos = { version: response.headers['x-relution-version'], description: response.headers['x-server'] }; if (response.statusCode === 503 || response.statusCode === 500 && error.className === 'java.util.concurrent.TimeoutException') { // 503 (service unavailable) indicates the server is temporarily overloaded, and unable // handling the request. This happens when async delegation timed out on the Java side, // usually after about 2 minutes. In this case retry the request until we are done... diag.debug.info('server overloaded, retrying request.'); // here promiseResponse must have been resolved already, // we chain anyways because of error propagation promiseResponse.thenResolve(ajax(options)).done(resolveResult, rejectResult); return; // early exit as result is handled by done call above } else { // logon session processing var sessionUserUuid = response.headers['x-gofer-user']; if (sessionUserUuid) { serverObj.sessionUserUuid = sessionUserUuid; } else if (response.statusCode === 401) { // apparently our session is lost! serverObj.sessionUserUuid = null; diag.debug.assert(function () { return !!error; }); diag.debug.warn('server session is lost!', error); var credentials = serverObj.credentials; if (credentials) { // recover by attempting login, // here promiseResponse must have been resolved already, // we chain anyways because of error propagation serverObj.credentials = null; promiseResponse.thenResolve(login(credentials, { serverUrl: serverUrl }).then(function () { diag.debug.assert(function () { return !!serverObj.sessionUserUuid; }); diag.debug.info('server session recovered.'); return ajax(options); })).done(resolveResult, rejectResult); return; // early exit as result is handled by done call above } } } } // completes the chain propagating results, must be skipped when request is retried above if (promiseResponse) { promiseResponse.then(function (responseResult) { diag.debug.assert(function () { return responseResult === resp; }, 'definition of behavior in case ' + 'of proxying the original response is reserved for future extension!'); if (error) { rejectResult(error); } else { resolveResult(body); } }, function (responseError) { rejectResult(responseError); }).done(); } }); } catch (error) { // path taken when request.js throws return rejectResult(error); } // transport response try { Q(requestCallback(req)).then(function (request) { if (request === void 0) { request = req; } request.on('response', function (response) { if (!resp) { resp = response; resolveResponse(responseCallback(resp)); } }); return request; }).done(); } catch (error) { // path taken when requestCallback throws return rejectResponse(error); } }); }); } exports.ajax = ajax; /** * logs into a Relution server. * * Notice, specifying `offlineCapable=true` in the options will store the login response locally on * the device when online and the login succeeded. When offline, the option will reuse the stored * response. Data encryption is used guaranteeing both secrecy of login data and verification of * the credentials provided. * * @param credentials to use. * @param loginOptions overwriting [[init]] defaults. * @return {Q.Promise<LoginResponse>} of login response. * * @example * ```javascript * * import * as Relution from 'relution-sdk'; * //config * Relution.init({ * serverUrl: 'http://localhost:8080' * }); * * let credentials = { * userName: 'myusername', * password: 'mypassword' * }; * * //usage * * // Promise * Relution.web.login(credentials) * .then((resp) => console.log('resp', resp);) * .catch((e:Error) => console.error(e.message, e)) * .finally(() => console.log('complete')); * * //Observable * Observable.fromPromise(Relution.web.login(credentials)).subscribe( * (resp: any) => console.log('resp', resp), * (e:Error) => console.error(e.message, e);, * () => console.log('complete') * ) * ``` */ function login(credentials, loginOptions) { if (loginOptions === void 0) { loginOptions = {}; } var wasOfflineLogin = false; var serverUrl = urls.resolveServer('/', loginOptions); var serverObj = server.Server.getInstance(serverUrl); if (serverObj.sessionUserUuid) { // logged in already return logout({ serverUrl: serverUrl }).then(function () { diag.debug.assert(function () { return !serverObj.sessionUserUuid; }); return login(credentials, loginOptions); // repeat after logout }); } else if (serverObj.credentials) { // had credentials but no session, so we were logged in offline wasOfflineLogin = true; } // process options var currentOptions = serverObj.applyOptions({ serverUrl: serverUrl, agentOptions: loginOptions.agentOptions || init.initOptions.agentOptions, agentClass: loginOptions.agentClass || init.initOptions.agentClass, // options taking effect at login time clientApp: loginOptions.clientApp || init.initOptions.clientApp, logonCallback: loginOptions.logonCallback || init.initOptions.logonCallback, clientCertificate: loginOptions.clientCertificate || init.initOptions.clientCertificate }); var logonCallback = currentOptions.logonCallback || _.identity; return ajax(_.defaults({ serverUrl: serverUrl, method: 'POST', url: '/gofer/security/rest/auth/login', body: credentials }, currentOptions)).then(function (response) { // real physical logon, ajax call sets sessionUserUuid if (!serverObj.sessionUserUuid) { diag.debug.warn('BUG: Relution did not set X-Gofer-User response header'); serverObj.sessionUserUuid = response.user.uuid; } diag.debug.assert(function () { return serverObj.sessionUserUuid === response.user.uuid; }); return response; }, function (error) { // offline login response if (!error.statusCode && loginOptions.offlineCapable) { // ajax timeout -> offline login attempt diag.debug.assert(function () { return !serverObj.sessionUserUuid; }, 'no physical login, as otherwise logonCallback would be executed'); return offline.fetchOfflineLogin(credentials, currentOptions).then(function (loginResponse) { if (!loginResponse) { // when there is no persistent data available, aka. this is the initial login attempt, // keep saying the server is offline... return Q.reject(error); } return loginResponse; }, function (offlineError) { // most likely the password entered was incorrect, // make sure the offlineError indicates the server is unavailable as well diag.debug.assert(function () { return !offlineError.statusCode; }); diag.debug.assert(function () { return !offlineError.requestUrl; }); offlineError.requestUrl = error.requestUrl; diag.debug.assert(function () { return !offlineError.cause; }); offlineError.cause = error; // we rethrow the annotated error of decoding the stored response, // because the network error just indicates we are offline and does // not mention the credentials being incorrect as this one does... return Q.reject(offlineError); }); } else if (error.statusCode && wasOfflineLogin) { // server side rejection, clear login data so that subsequent offline logins fail as well return offline.clearOfflineLogin(credentials, currentOptions).catch(function (offlineError) { // this is bad but we can not do much about it diag.debug.warn('failed erasing offline login data', offlineError); return Q.reject(error); }); } return Q.reject(error); }).then(function (response) { // switch current server if ('roles' in response.roles) { // fixes a defect of Java implementation response.roles = response.roles['roles']; } serverObj.authorization = { name: response.user.uuid, roles: _.map(response.roles, function (role) { return role.uuid; }) }; serverObj.organization = response.organization; serverObj.user = response.user; serverObj.credentials = credentials; server.setCurrentServer(serverObj); return response; }).then(function (response) { // this is the earliest point at which library state reflects correct authorization, etc. // Thus, the logonCallback may execute here now, but only if we are online actually... if (!serverObj.sessionUserUuid) { return response; // offline } // we have a session logged into this user diag.debug.assert(function () { return serverObj.sessionUserUuid === server.getCurrentAuthorization().name; }); // run logonCallback on response data and eventually store resultant object for offline login, // because this way the callback may add information to the response object that will also be // persisted and made available again when offline! return Q(logonCallback(response)).then(function (logonInfos) { if (logonInfos === void 0) { logonInfos = response; } if (logonInfos && logonInfos !== response) { // any data returned by the logonCallback may be stored here response.logonInfos = logonInfos; } // store offline login response if (loginOptions.offlineCapable || wasOfflineLogin) { // initial store or update of login data return offline.storeOfflineLogin(credentials, currentOptions, response).catch(function (offlineError) { diag.debug.warn('offline login store failed', offlineError); return response; }); } return response; }, function (error) { // logon callback failed, must logout to avoid making ajax calls in an unknown backend state return logout({ serverUrl: serverUrl }).catch(function (logoutError) { diag.debug.error('failed to logout after login failure', logoutError); return Q.reject(error); }).finally(function () { // logout processing must leave us with no user session diag.debug.assert(function () { return !serverObj.sessionUserUuid; }); }); }); }); } exports.login = login; ; /** * logs out of a Relution server. * * For explicit logouts (trigger by app user pressing a logout button, for example) specifying * `offlineCapable = true` will drop any persisted offline login data for the server logging out * of. * * @param logoutOptions overwriting [[init]] defaults. * @return {Q.Promise<void>} of logout response. * * @example * ```javascript * * Relution.web.logout() * .then((resp) => console.log('resp', resp);) * .catch((e:Error) => console.error(e.message, e)) * .finally(() => console.log('bye bye')); * * //Observable * Observable.fromPromise(Relution.web.logout()).subscribe( * (resp: any) => console.log('resp', resp), * (e:Error) => console.error(e.message, e);, * () => console.log('bye bye') * ) * ``` */ function logout(logoutOptions) { if (logoutOptions === void 0) { logoutOptions = {}; } var serverUrl = urls.resolveServer('/', logoutOptions); var serverObj = server.Server.getInstance(serverUrl); // process options var currentOptions = serverObj.applyOptions({ serverUrl: serverUrl, agentOptions: logoutOptions.agentOptions || init.initOptions.agentOptions, agentClass: logoutOptions.agentClass || init.initOptions.agentClass, }); return ajax(_.defaults({ serverUrl: serverUrl, method: 'POST', url: '/gofer/security/rest/auth/logout', body: {} }, currentOptions)).catch(function (error) { if (error.statusCode === 422) { // REST-based logout URL currently is broken reporting a 422 in all cases return ajax(_.defaults({ serverUrl: serverUrl, method: 'GET', url: '/gofer/security-logout' }, currentOptions)).then(function (result) { diag.debug.warn('BUG: resorted to classic PATH-based logout as REST-based logout failed:', error); return result; }, function (error2) { return Q.reject(error2.statusCode === 422 ? error : error2); }); } return Q.reject(error); }).catch(function (error) { // ignore network failures on timeout, server forgets on session timeout anyways if (!error.statusCode) { return Q.resolve(undefined); } return Q.reject(error); }).finally(function () { // eventually erase offline login data if (logoutOptions.offlineCapable) { // requested to erase login data return offline.clearOfflineLogin(serverObj.credentials, currentOptions).catch(function (offlineError) { diag.debug.warn('failed erasing offline login data', offlineError); return Q.resolve(undefined); }); } }).finally(function () { // forget everything about it serverObj.credentials = null; serverObj.authorization = auth.ANONYMOUS_AUTHORIZATION; serverObj.organization = null; serverObj.user = null; serverObj.sessionUserUuid = null; }); } exports.logout = logout; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy93ZWIvaHR0cC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBQ0g7O0dBRUc7QUFDSCxNQUFNOztBQUVOLElBQVksQ0FBQyxXQUFNLEdBQUcsQ0FBQyxDQUFBO0FBQ3ZCLElBQVksQ0FBQyxXQUFNLFFBQVEsQ0FBQyxDQUFBO0FBQzVCLElBQVksSUFBSSxXQUFNLGtCQUFrQixDQUFDLENBQUE7QUFDekMsSUFBWSxJQUFJLFdBQU0sY0FBYyxDQUFDLENBQUE7QUFDckMsSUFBWSxJQUFJLFdBQU0sTUFBTSxDQUFDLENBQUE7QUFDN0IsSUFBWSxJQUFJLFdBQU0sY0FBYyxDQUFDLENBQUE7QUFDckMsSUFBWSxPQUFPLFdBQU0sV0FBVyxDQUFDLENBQUE7QUFDckMsSUFBWSxPQUFPLFdBQU0sU0FBUyxDQUFDLENBQUE7QUFFbkMsSUFBWSxNQUFNLFdBQU0sb0JBQW9CLENBQUMsQ0FBQTtBQUM3QyxJQUFZLElBQUksV0FBTSxRQUFRLENBQUMsQ0FBQTtBQUUvQiw4Q0FBOEM7QUFDOUMsSUFBSSxlQUFlLEdBQUc7SUFDcEIsSUFBSSxFQUFFLElBQUk7SUFDVixHQUFHLEVBQUUsSUFBSTtJQUNULGVBQWUsRUFBRSxJQUFJO0NBQ3RCLENBQUM7QUFDRixJQUFJLG1CQUFtQixHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUMsZUFBZSxDQUFDLENBQUM7QUE4RjVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTRDRztBQUNILGNBQXdCLE9BQW9CO0lBQzFDLElBQUksU0FBUyxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUN6RCxJQUFJLFNBQVMsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUNy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LENBQUMsQ0FBQyxPQUFPLENBQUM7UUFDVCxzQ0FBc0M7UUFDdEMsRUFBRSxDQUFDLENBQUMsYUFBYSxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUM7WUFDakMsZ0NBQWdDO1lBQ2hDLE1BQU0sQ0FBQyxPQUFPLENBQUMsaUJBQWlCLENBQUMsU0FBUyxDQUFDLFdBQVcsRUFBRSxjQUFjLENBQUMsQ0FBQyxLQUFLLENBQzNFLFVBQUMsWUFBWTtnQkFDYixJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxtQ0FBbUMsRUFBRSxZQUFZLENBQUMsQ0FBQztnQkFDbkUsTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQU8sU0FBUyxDQUFDLENBQUM7WUFDcEMsQ0FBQyxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDO1FBQ1QsNkJBQTZCO1FBQzdCLFNBQVMsQ0FBQyxXQUFXLEdBQUcsSUFBSSxDQUFDO1FBQzdCLFNBQVMsQ0FBQyxhQUFhLEdBQUcsSUFBSSxDQUFDLHVCQUF1QixDQUFDO1FBQ3ZELFNBQVMsQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDO1FBQzlCLFNBQVMsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1FBQ3RCLFNBQVMsQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDO0lBQ25DLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQXhEZSxjQUFNLFNBd0RyQixDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiLypcbiAqIEBmaWxlIHdlYi9odHRwLnRzXG4gKiBSZWx1dGlvbiBTREtcbiAqXG4gKiBDcmVhdGVkIGJ5IFRob21hcyBCZWNrbWFubiBvbiAyOC4wNC4yMDE2XG4gKiBDb3B5cmlnaHQgMjAxNiBNLVdheSBTb2x1dGlvbnMgR21iSFxuICpcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSBcIkxpY2Vuc2VcIik7XG4gKiB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuXG4gKiBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXRcbiAqXG4gKiBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjBcbiAqXG4gKiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlXG4gKiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiBcIkFTIElTXCIgQkFTSVMsXG4gKiBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC5cbiAqIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmRcbiAqIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLlxuICovXG4vKipcbiAqIEBtb2R1bGUgd2ViXG4gKi9cbi8qKiAqL1xuXG5pbXBvcnQgKiBhcyBRIGZyb20gJ3EnO1xuaW1wb3J0ICogYXMgXyBmcm9tICdsb2Rhc2gnO1xuaW1wb3J0ICogYXMgYXV0aCBmcm9tICcuLi9zZWN1cml0eS9hdXRoJztcbmltcG9ydCAqIGFzIGRpYWcgZnJvbSAnLi4vY29yZS9kaWFnJztcbmltcG9ydCAqIGFzIGh0dHAgZnJvbSAnaHR0cCc7XG5pbXBvcnQgKiBhcyBpbml0IGZyb20gJy4uL2NvcmUvaW5pdCc7XG5pbXBvcnQgKiBhcyBvZmZsaW5lIGZyb20gJy4vb2ZmbGluZSc7XG5pbXBvcnQgKiBhcyByZXF1ZXN0IGZyb20gJ3JlcXVlc3QnO1xuaW1wb3J0ICogYXMgcm9sZXMgZnJvbSAnLi4vc2VjdXJpdHkvcm9sZXMnO1xuaW1wb3J0ICogYXMgc2VydmVyIGZyb20gJy4uL3NlY3VyaXR5L3NlcnZlcic7XG5pbXBvcnQgKiBhcyB1cmxzIGZyb20gJy4vdXJscyc7XG5cbi8vIHJlcXVpcmUgcmVxdWVzdC5qcyB0byBtYW5hZ2UgY29va2llcyBmb3IgdXNcbmxldCByZXF1ZXN0RGVmYXVsdHMgPSB7XG4gIGpzb246IHRydWUsXG4gIGphcjogdHJ1ZSxcbiAgd2l0aENyZWRlbnRpYWxzOiB0cnVlXG59O1xubGV0IHJlcXVlc3RXaXRoRGVmYXVsdHMgPSByZXF1ZXN0LmRlZmF1bHRzKHJlcXVlc3REZWZhdWx0cyk7XG5cbi8qKlxuICogY2FsbGJhY2sgYWxsb3dpbmcgY3VzdG9taXppbmcgYW4gb2JqZWN0IG5vdCBpbW1lZGlhdGVseSBhdmFpbGFibGUgYXQgdGltZSBvZiBjYWxsLlxuICpcbiAqIEBwYXJhbSBvYmplY3QgZm9yIGluc3BlY3Rpb24gb3IgY3VzdG9taXphdGlvbi5cbiAqIEByZXR1cm4gcHJvbWlzZSBvciBvYmplY3Qgb24gc2FtZSBkZWZlcnJlZCBvYmplY3QuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSHR0cENhbGxiYWNrPFQ+IHtcbiAgKHZhbHVlOiBUKTogUS5Qcm9taXNlPFQ+IHwgVDtcbn1cblxuLyoqXG4gKiB0eXBlIHJlcHJlc2VudGluZyBhIHJhdyByZXF1ZXN0LlxuICovXG5leHBvcnQgdHlwZSBIdHRwUmVxdWVzdCA9IHJlcXVlc3QuUmVxdWVzdDtcbi8qKlxuICogdHlwZSByZXByZXNlbnRpbmcgYSByYXcgcmVzcG9uc2UuXG4gKi9cbmV4cG9ydCB0eXBlIEh0dHBSZXNwb25zZSA9IGh0dHAuSW5jb21pbmdNZXNzYWdlO1xuXG4vKipcbiAqIG5hbWVkIHBhcmFtZXRlcnMgb2YgdGhlIFtbaHR0cF1dIGZ1bmN0aW9uLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEh0dHBPcHRpb25zIGV4dGVuZHMgcmVxdWVzdC5Db3JlT3B0aW9ucywgcmVxdWVzdC5VcmxPcHRpb25zLFxuICAgIGluaXQuU2VydmVySW5pdE9wdGlvbnMge1xuICAvKipcbiAgICogb3B0aW9uYWwgY2FsbGJhY2sgYWxsb3dpbmcgdG8gY3VzdG9taXplIHRoZSBjbGllbnQgcmVxdWVzdCBpbiBtb3JlIGRldGFpbCB0aGFuIHByb3ZpZGVkIGJ5XG4gICAqIGRlZmF1bHQuXG4gICAqL1xuICByZXF1ZXN0Q2FsbGJhY2s/OiBIdHRwQ2FsbGJhY2s8SHR0cFJlcXVlc3Q+O1xuICAvKipcbiAgICogb3B0aW9uYWwgY2FsbGJhY2sgYWxsb3dpbmcgdG8gaW5zcGVjdCB0aGUgc2VydmVyIHJlc3BvbnNlIGluIG1vcmUgZGV0YWlsIHRoYW4gcHJvdmlkZWQgYnlcbiAgICogZGVmYXVsdC5cbiAgICovXG4gIHJlc3BvbnNlQ2FsbGJhY2s/OiBIdHRwQ2FsbGJhY2s8SHR0cFJlc3BvbnNlPjtcbn1cblxuLyoqXG4gKiBmYWlsdXJlIG9mIGFuIGFqYXggcmVxdWVzdC5cbiAqXG4gKiBUaGlzIHR5cGUgY2FuIGJlIHVzZWQgYXMgdHlwZSBhbm5vdGF0aW9uIG9mIHRoZSBlcnJvciB0aGUgUHJvbWlzZSByZXR1cm5lZCBieSBhamF4IGlzIHJlamVjdGVkXG4gKiB3aXRoLlxuICpcbiAqIEBzZWUgYWpheFxuICovXG5leHBvcnQgaW50ZXJmYWNlIEh0dHBFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgLyoqXG4gICAqIGZ1bGx5IHJlc29sdmVkIHVybCB0aGUgcmVxdWVzdCB3YXMgc2VudCB0by5cbiAgICovXG4gIHJlcXVlc3RVcmw/OiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIEhUVFAgc3RhdHVzIGNvZGUgb2YgZmFpbHVyZS5cbiAgICovXG4gIHN0YXR1c0NvZGU/OiBudW1iZXI7XG4gIC8qKlxuICAgKiBIVFRQIHN0YXR1cyBtZXNzYWdlIG9mIGZhaWx1cmUuXG4gICAqL1xuICBzdGF0dXNNZXNzYWdlPzogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBpbiBtYW55IGNhc2VzIHRoZSBSZWx1dGlvbiBzZXJ2ZXIgcmVwb3J0cyBoZXJlIHRoZSBmdWxseSBxdWFsaWZpZWQgbmFtZSBvZiBhIEphdmEgRXhjZXB0aW9uXG4gICAqIHRoYXQgbWF5IGJlIHVzZWQgdG8gZnVydGhlciBkaWZmZXJlbnRpYXRlIHRoZSBlcnJvci5cbiAgICovXG4gIGNsYXNzTmFtZT86IHN0cmluZztcbiAgLyoqXG4gICAqIG1heSBiZSBzZXQgdG8gc29tZSBhcmJpdHJhcnkgdmFsdWUgZGVzY3JpYmluZyB0aGUgY2F1c2Ugb2YgZmFpbHVyZSwgbW9zdGx5IHByZXNlbnQgd2hlblxuICAgKiB0cmFuc3BvcnRpbmcgSmF2YSBFeGNlcHRpb24gb2JqZWN0cy5cbiAgICovXG4gIGNhdXNlPzogYW55O1xuXG4gIC8qKlxuICAgKiBkZXRhaWxzIG9mIHJlcXVlc3QgZmFpbGVkLlxuICAgKlxuICAgKiBUaGlzIGlzIGEgbm9uLWVudW1lcmFibGUgcHJvcGVydHkgYW5kIHRodXMgbm90IHBhcnQgb2YgdGhlIEpTT04gcmVwcmVzZW50YXRpb24gb2YgdGhlIGZhaWx1cmUuXG4gICAqIEl0IGlzIHByb3ZpZGVkIGZvciBpbmZvcm1hbCBwdXJwb3NlcyBhcyBhIGRlYnVnZ2luZyBhaWQgb25seS4gQ2xpZW50IGNvZGUgc2hvdWxkIG5vdCByZWx5IG9uXG4gICAqIHRoaXMgdmFsdWUuXG4gICAqXG4gICAqIEBzZWUgcmVzcG9uc2VcbiAgICov