UNPKG

@sharekey/meteor-desktop

Version:

Build a Meteor's desktop client with hot code push.

740 lines (706 loc) 114 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _runtime = _interopRequireDefault(require("regenerator-runtime/runtime")); var _asar = _interopRequireDefault(require("@electron/asar")); var _assignIn = _interopRequireDefault(require("lodash/assignIn")); var _lodash = _interopRequireDefault(require("lodash")); var _installLocal = require("install-local"); var _core = require("@babel/core"); var _crypto = _interopRequireDefault(require("crypto")); var _del = _interopRequireDefault(require("del")); var _presetEnv = _interopRequireDefault(require("@babel/preset-env")); var _fs = _interopRequireDefault(require("fs")); var _path = _interopRequireDefault(require("path")); var _shelljs = _interopRequireDefault(require("shelljs")); var _semver = _interopRequireDefault(require("semver")); var _terser = _interopRequireDefault(require("terser")); var _log = _interopRequireDefault(require("./log")); var _electronAppScaffold = _interopRequireDefault(require("./electronAppScaffold")); var _dependenciesManager = _interopRequireDefault(require("./dependenciesManager")); var _binaryModulesDetector = _interopRequireDefault(require("./binaryModulesDetector")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // eslint-disable-next-line no-unused-vars _shelljs.default.config.fatal = true; /** * Represents the .desktop dir scaffold. * @class */ class ElectronApp { /** * @param {MeteorDesktop} $ - context * @constructor */ constructor($) { this.log = new _log.default('electronApp'); this.scaffold = new _electronAppScaffold.default($); this.depsManager = new _dependenciesManager.default($, this.scaffold.getDefaultPackageJson().dependencies); this.$ = $; this.meteorApp = this.$.meteorApp; this.packageJson = null; this.version = null; this.compatibilityVersion = null; this.deprectatedPlugins = ['meteor-desktop-localstorage']; } /** * Makes an app.asar from the skeleton app. * @property {Array} excludeFromDel - list of paths to exclude from deleting * @returns {Promise} */ packSkeletonToAsar(excludeFromDel = []) { this.log.info('packing skeleton app and node_modules to asar archive'); return new Promise(resolve => { const extract = this.getModulesToExtract(); // We want to pack skeleton app and node_modules together, so we need to temporarily // move node_modules to app dir. this.log.debug('moving node_modules to app dir'); _fs.default.renameSync(this.$.env.paths.electronApp.nodeModules, _path.default.join(this.$.env.paths.electronApp.appRoot, 'node_modules')); let extracted = false; extracted = this.extractModules(extract); this.log.debug('packing'); _asar.default.createPackage(this.$.env.paths.electronApp.appRoot, this.$.env.paths.electronApp.appAsar).then(() => { // Lets move the node_modules back. this.log.debug('moving node_modules back from app dir'); _shelljs.default.mv(_path.default.join(this.$.env.paths.electronApp.appRoot, 'node_modules'), this.$.env.paths.electronApp.nodeModules); if (extracted) { // We need to create a full node modules back. In other words we want // the extracted modules back. extract.forEach(module => _shelljs.default.cp('-rf', _path.default.join(this.$.env.paths.electronApp.extractedNodeModules, module), _path.default.join(this.$.env.paths.electronApp.nodeModules, module))); // Get the .bin back. if (this.$.utils.exists(this.$.env.paths.electronApp.extractedNodeModulesBin)) { _shelljs.default.cp(_path.default.join(this.$.env.paths.electronApp.extractedNodeModulesBin, '*'), _path.default.join(this.$.env.paths.electronApp.nodeModules, '.bin')); } } this.log.debug('deleting source files'); const exclude = [this.$.env.paths.electronApp.nodeModules].concat([this.$.env.paths.electronApp.appAsar, this.$.env.paths.electronApp.packageJson], excludeFromDel); _del.default.sync([`${this.$.env.paths.electronApp.root}${_path.default.sep}*`].concat(exclude.map(pathToExclude => `!${pathToExclude}`)), { force: true }); resolve(); }); }); } /** * Moves specified node modules to a separate directory. * @param {Array} extract * @returns {boolean} */ extractModules(extract) { const ext = ['.js', '.bat', '.sh', '.cmd', '']; if (extract.length > 0) { if (this.$.utils.exists(this.$.env.paths.electronApp.extractedNodeModules)) { _shelljs.default.rm('-rf', this.$.env.paths.electronApp.extractedNodeModules); } _fs.default.mkdirSync(this.$.env.paths.electronApp.extractedNodeModules); _fs.default.mkdirSync(this.$.env.paths.electronApp.extractedNodeModulesBin); extract.forEach(module => { _fs.default.renameSync(_path.default.join(this.$.env.paths.electronApp.appRoot, 'node_modules', module), _path.default.join(this.$.env.paths.electronApp.extractedNodeModules, module)); // Move bins. this.extractBin(module, ext); }); return true; } return false; } /** * Extracts the bin files associated with a certain node modules. * * @param module * @param ext */ extractBin(module, ext) { let packageJson; try { packageJson = JSON.parse(_fs.default.readFileSync(_path.default.join(this.$.env.paths.electronApp.extractedNodeModules, module, 'package.json'), 'utf8')); } catch (e) { packageJson = {}; } const bins = 'bin' in packageJson && typeof packageJson.bin === 'object' ? Object.keys(packageJson.bin) : []; if (bins.length > 0) { bins.forEach(bin => { ext.forEach(extension => { const binFilePath = _path.default.join(this.$.env.paths.electronApp.appRoot, 'node_modules', '.bin', `${bin}${extension}`); if (this.$.utils.exists(binFilePath) || this.$.utils.symlinkExists(binFilePath)) { _fs.default.renameSync(binFilePath, _path.default.join(this.$.env.paths.electronApp.extractedNodeModulesBin, `${bin}${extension}`)); } }); }); } } /** * Merges the `extract` field with automatically detected modules. */ getModulesToExtract() { const binaryModulesDetector = new _binaryModulesDetector.default(this.$.env.paths.electronApp.nodeModules); const toBeExtracted = binaryModulesDetector.detect(); let { extract } = this.$.desktop.getSettings(); if (!Array.isArray(extract)) { extract = []; } const merge = {}; toBeExtracted.concat(extract).forEach(module => { merge[module] = true; }); extract = Object.keys(merge); if (extract.length > 0) { this.log.verbose(`resultant modules to extract list is: ${extract.join(', ')}`); } return extract; } /** * Calculates a md5 from all dependencies. */ calculateCompatibilityVersion() { this.log.verbose('calculating compatibility version'); const settings = this.$.desktop.getSettings(); if ('desktopHCPCompatibilityVersion' in settings) { this.compatibilityVersion = `${settings.desktopHCPCompatibilityVersion}`; this.log.warn(`compatibility version overridden to ${this.compatibilityVersion}`); return; } const md5 = _crypto.default.createHash('md5'); let dependencies = this.depsManager.getDependencies(); const dependenciesSorted = Object.keys(dependencies).sort(); dependencies = dependenciesSorted.map(dependency => `${dependency}:${dependencies[dependency]}`); const mainCompatibilityVersion = this.$.getVersion().split('.'); this.log.debug('meteor-desktop compatibility version is ', `${mainCompatibilityVersion[0]}`); dependencies.push(`meteor-desktop:${mainCompatibilityVersion[0]}`); const desktopCompatibilityVersion = settings.version.split('.')[0]; this.log.debug('.desktop compatibility version is ', desktopCompatibilityVersion); dependencies.push(`desktop-app:${desktopCompatibilityVersion}`); if (process.env.METEOR_DESKTOP_DEBUG_DESKTOP_COMPATIBILITY_VERSION || process.env.METEOR_DESKTOP_DEBUG) { this.log.debug(`compatibility version calculated from ${JSON.stringify(dependencies)}`); } md5.update(JSON.stringify(dependencies)); this.compatibilityVersion = md5.digest('hex'); } async init() { try { await this.$.electron.init(); await this.$.electronBuilder.init(); } catch (e) { this.log.warn('error occurred while initialising electron and electron-builder integration', e); process.exit(1); } } /** * Runs all necessary tasks to build the desktopified app. */ async build(run = false) { // TODO: refactor to a task runner this.log.info('scaffolding'); if (!this.$.desktop.check()) { if (!this.$.env.options.scaffold) { this.log.error('seems that you do not have a .desktop dir in your project or it is' + ' corrupted. Run \'npm run desktop -- init\' to get a new one.'); // Do not fail, so that npm will not print his error stuff to console. process.exit(0); } else { this.$.desktop.scaffold(); this.$.meteorApp.updateGitIgnore(); } } await this.init(); try { this.$.meteorApp.updateGitIgnore(); } catch (e) { this.log.warn(`error occurred while adding ${this.$.env.paths.electronApp.rootName}` + 'to .gitignore: ', e); } try { await this.$.meteorApp.removeDeprecatedPackages(); } catch (e) { this.log.error('error while removing deprecated packages: ', e); process.exit(1); } try { await this.$.meteorApp.ensureDesktopHCPPackages(); } catch (e) { this.log.error('error while checking for required packages: ', e); process.exit(1); } try { await this.scaffold.make(); } catch (e) { this.log.error('error while scaffolding: ', e); process.exit(1); } try { const fileName = '.npmrc'; const dirName = '.meteor/desktop-build'; if (_fs.default.existsSync(dirName) && _fs.default.existsSync(fileName)) { _fs.default.copyFileSync(fileName, `${dirName}/${fileName}`); } } catch (e) { this.log.warn('error while copying .npmrc', e); } try { await this.exposeElectronModules(); } catch (e) { this.log.error('error while exposing electron modules: ', e); process.exit(1); } try { this.updatePackageJsonFields(); } catch (e) { this.log.error('error while updating package.json: ', e); } try { this.updateDependenciesList(); } catch (e) { this.log.error('error while merging dependencies list: ', e); } try { this.calculateCompatibilityVersion(); } catch (e) { this.log.error('error while calculating compatibility version: ', e); process.exit(1); } try { await this.handleTemporaryNodeModules(); } catch (e) { this.log.error('error occurred while handling temporary node_modules: ', e); process.exit(1); } let nodeModulesRemoved; try { nodeModulesRemoved = await this.handleStateOfNodeModules(); } catch (e) { this.log.error('error occurred while clearing node_modules: ', e); process.exit(1); } try { await this.rebuildDeps(true); } catch (e) { this.log.error('error occurred while installing node_modules: ', e); process.exit(1); } if (!nodeModulesRemoved) { try { await this.rebuildDeps(); } catch (e) { this.log.error('error occurred while rebuilding native node modules: ', e); process.exit(1); } } try { await this.linkNpmPackages(); } catch (e) { this.log.error(`linking packages failed: ${e}`); process.exit(1); } try { await this.installLocalNodeModules(); } catch (e) { this.log.error('error occurred while installing local node modules: ', e); process.exit(1); } try { await this.ensureMeteorDependencies(); } catch (e) { this.log.error('error occurred while ensuring meteor dependencies are installed: ', e); process.exit(1); } if (this.$.env.isProductionBuild()) { try { await this.packSkeletonToAsar(); } catch (e) { this.log.error('error while packing skeleton to asar: ', e); process.exit(1); } } // TODO: find a way to avoid copying .desktop to a temp location try { this.copyDesktopToDesktopTemp(); } catch (e) { this.log.error('error while copying .desktop to a temporary location: ', e); process.exit(1); } try { await this.updateSettingsJsonFields(); } catch (e) { this.log.error('error while updating settings.json: ', e); process.exit(1); } try { await this.excludeFilesFromArchive(); } catch (e) { this.log.error('error while excluding files from packing to asar: ', e); process.exit(1); } try { await this.transpileAndMinify(); } catch (e) { this.log.error('error while transpiling or minifying: ', e); } try { await this.packDesktopToAsar(); } catch (e) { this.log.error('error occurred while packing .desktop to asar: ', e); process.exit(1); } try { await this.getMeteorClientBuild(); } catch (e) { this.log.error('error occurred during getting meteor mobile build: ', e); } if (run) { this.log.info('running'); this.$.electron.run(); } else { this.log.info('built'); } } /** * Copies the `exposedModules` setting from `settings.json` into `preload.js` modifying its code * so that the script will have it hardcoded. */ exposeElectronModules() { const { exposedModules } = this.$.desktop.getSettings(); if (exposedModules && Array.isArray(exposedModules) && exposedModules.length > 0) { let preload = _fs.default.readFileSync(this.$.env.paths.electronApp.preload, 'utf8'); const modules = this.$.desktop.getSettings().exposedModules.reduce( // eslint-disable-next-line no-return-assign,no-param-reassign (prev, module) => (prev += `'${module}', `, prev), ''); preload = preload.replace('const exposedModules = [', `const exposedModules = [${modules}`); _fs.default.writeFileSync(this.$.env.paths.electronApp.preload, preload); } } /** * Ensures all required dependencies are added to the Meteor project. * @returns {Promise.<void>} */ async ensureMeteorDependencies() { let packages = []; const packagesWithVersion = []; let plugins = 'plugins ['; Object.keys(this.$.desktop.getDependencies().plugins).forEach(plugin => { // Read package.json of the plugin. const packageJson = JSON.parse(_fs.default.readFileSync(_path.default.join(this.$.env.paths.electronApp.nodeModules, plugin, 'package.json'), 'utf8')); if ('meteorDependencies' in packageJson && typeof packageJson.meteorDependencies === 'object') { plugins += `${plugin}, `; packages.unshift(...Object.keys(packageJson.meteorDependencies)); packagesWithVersion.unshift(...packages.map(packageName => { if (packageJson.meteorDependencies[packageName] === '@version') { return `${packageName}@${packageJson.version}`; } return `${packageName}@${packageJson.meteorDependencies[packageName]}`; })); } }); const packagesCount = packages.length; packages = packages.filter(value => !this.deprectatedPlugins.includes(value)); if (packagesCount !== packages.length) { this.log.warn('you have some deprecated meteor desktop plugins in your settings, please remove ' + `them (deprecated plugins: ${this.deprectatedPlugins.join(', ')})`); } if (packages.length > 0) { plugins = `${plugins.substr(0, plugins.length - 2)}]`; try { await this.$.meteorApp.meteorManager.ensurePackages(packages, packagesWithVersion, plugins); } catch (e) { throw new Error(e); } } } /** * Builds meteor app. */ async getMeteorClientBuild() { await this.$.meteorApp.build(); } /** * Removes node_modules if needed. * @returns {Promise<void>} */ async handleStateOfNodeModules() { if (this.$.env.isProductionBuild() || this.$.env.options.ia32) { if (!this.$.env.isProductionBuild()) { this.log.info('clearing node_modules because we need to have it clear for ia32 rebuild'); } else { this.log.info('clearing node_modules because this is a production build'); } try { await this.$.utils.rmWithRetries('-rf', this.$.env.paths.electronApp.nodeModules); } catch (e) { throw new Error(e); } return true; } return false; } /** * If there is a temporary node_modules folder and no node_modules folder, we will * restore it, as it might be a leftover from an interrupted flow. * @returns {Promise<void>} */ async handleTemporaryNodeModules() { if (this.$.utils.exists(this.$.env.paths.electronApp.tmpNodeModules)) { if (!this.$.utils.exists(this.$.env.paths.electronApp.nodeModules)) { this.log.debug('moving temp node_modules back'); _shelljs.default.mv(this.$.env.paths.electronApp.tmpNodeModules, this.$.env.paths.electronApp.nodeModules); } else { // If there is a node_modules folder, we should clear the temporary one. this.log.debug('clearing temp node_modules because new one is already created'); try { await this.$.utils.rmWithRetries('-rf', this.$.env.paths.electronApp.tmpNodeModules); } catch (e) { throw new Error(e); } } } } /** * Runs npm link for every package specified in settings.json->linkPackages. */ async linkNpmPackages() { if (this.$.env.isProductionBuild()) { return; } const settings = this.$.desktop.getSettings(); const promises = []; if ('linkPackages' in this.$.desktop.getSettings()) { if (Array.isArray(settings.linkPackages)) { settings.linkPackages.forEach(packageName => promises.push(this.$.meteorApp.runNpm(['link', packageName], undefined, this.$.env.paths.electronApp.root))); } } await Promise.all(promises); } /** * Runs npm in the electron app to get the dependencies installed. * @returns {Promise} */ async ensureDeps() { this.log.info('installing dependencies'); if (this.$.utils.exists(this.$.env.paths.electronApp.nodeModules)) { this.log.debug('running npm prune to wipe unneeded dependencies'); try { await this.runNpm(['prune']); } catch (e) { throw new Error(e); } } try { await this.runNpm(['install'], this.$.env.stdio); } catch (e) { throw new Error(e); } } /** * Warns if plugins version are outdated in compare to the newest scaffold. * @param {Object} pluginsVersions - current plugins versions from settings.json */ checkPluginsVersion(pluginsVersions) { const settingsJson = JSON.parse(_fs.default.readFileSync(_path.default.join(this.$.env.paths.scaffold, 'settings.json'))); const scaffoldPluginsVersion = this.$.desktop.getDependencies(settingsJson, false).plugins; Object.keys(pluginsVersions).forEach(pluginName => { if (pluginName in scaffoldPluginsVersion && scaffoldPluginsVersion[pluginName] !== pluginsVersions[pluginName] && _semver.default.lt(pluginsVersions[pluginName], scaffoldPluginsVersion[pluginName])) { this.log.warn(`you are using outdated version ${pluginsVersions[pluginName]} of ` + `${pluginName}, the suggested version to use is ` + `${scaffoldPluginsVersion[pluginName]}`); } }); } /** * Merges core dependency list with the dependencies from .desktop. */ updateDependenciesList() { this.log.info('updating list of package.json\'s dependencies'); const desktopDependencies = this.$.desktop.getDependencies(); this.checkPluginsVersion(desktopDependencies.plugins); this.log.debug('merging settings.json[dependencies]'); this.depsManager.mergeDependencies('settings.json[dependencies]', desktopDependencies.fromSettings); this.log.debug('merging settings.json[plugins]'); this.depsManager.mergeDependencies('settings.json[plugins]', desktopDependencies.plugins); this.log.debug('merging dependencies from modules'); Object.keys(desktopDependencies.modules).forEach(module => this.depsManager.mergeDependencies(`module[${module}]`, desktopDependencies.modules[module])); this.packageJson.dependencies = this.depsManager.getRemoteDependencies(); this.packageJson.localDependencies = this.depsManager.getLocalDependencies(); this.log.debug('writing updated package.json'); _fs.default.writeFileSync(this.$.env.paths.electronApp.packageJson, JSON.stringify(this.packageJson, null, 2)); } /** * Install node modules from local paths using local-install. * * @param {string} arch * @returns {Promise} */ installLocalNodeModules(arch = this.$.env.options.ia32 || process.arch === 'ia32' ? 'ia32' : 'x64') { const localDependencies = _lodash.default.values(this.packageJson.localDependencies); if (localDependencies.length === 0) { return Promise.resolve(); } this.log.info('installing local node modules'); const lastRebuild = this.$.electronBuilder.prepareLastRebuildObject(arch); const env = this.$.electronBuilder.getGypEnv(lastRebuild.frameworkInfo, lastRebuild.platform, lastRebuild.arch); const installer = new _installLocal.LocalInstaller({ [this.$.env.paths.electronApp.root]: localDependencies }, { npmEnv: env }); (0, _installLocal.progress)(installer); return installer.install(); } /** * Rebuild binary dependencies against Electron's node headers. * @returns {Promise} */ rebuildDeps(install = false) { if (install) { this.log.info('issuing node_modules install from electron-builder'); } else { this.log.info('issuing native modules rebuild from electron-builder'); } const arch = this.$.env.options.ia32 || process.arch === 'ia32' ? 'ia32' : 'x64'; if (this.$.env.options.ia32) { this.log.verbose('forcing rebuild for 32bit'); } else { this.log.verbose(`rebuilding for ${arch}`); } return this.$.electronBuilder.installOrRebuild(arch, undefined, install); } /** * Update package.json fields accordingly to what is set in settings.json. * * packageJson.name = settings.projectName * packageJson.version = settings.version * packageJson.* = settings.packageJsonFields */ updatePackageJsonFields() { this.log.verbose('updating package.json fields'); const settings = this.$.desktop.getSettings(); /** @type {desktopSettings} */ const packageJson = this.scaffold.getDefaultPackageJson(); packageJson.version = settings.version; if ('packageJsonFields' in settings) { (0, _assignIn.default)(packageJson, settings.packageJsonFields); } (0, _assignIn.default)(packageJson, { name: settings.projectName }); this.log.debug('writing updated package.json'); _fs.default.writeFileSync(this.$.env.paths.electronApp.packageJson, JSON.stringify(packageJson, null, 4)); this.packageJson = packageJson; } /** * Updates settings.json with env (prod/dev) information and versions. */ async updateSettingsJsonFields() { this.log.debug('updating settings.json fields'); const settings = this.$.desktop.getSettings(); // Save versions. settings.compatibilityVersion = this.compatibilityVersion; // Pass information about build type to the settings.json. settings.env = this.$.env.isProductionBuild() ? 'prod' : 'dev'; const version = await this.$.desktop.getHashVersion(); settings.desktopVersion = `${version}_${settings.env}`; settings.meteorDesktopVersion = this.$.getVersion(); if (this.$.env.options.prodDebug) { settings.prodDebug = true; } _fs.default.writeFileSync(this.$.env.paths.desktopTmp.settings, JSON.stringify(settings, null, 4)); } /** * Copies files from prepared .desktop to desktop.asar in electron app. */ packDesktopToAsar() { this.log.info('packing .desktop to asar'); return new Promise((resolve, reject) => { _asar.default.createPackage(this.$.env.paths.desktopTmp.root, this.$.env.paths.electronApp.desktopAsar).then(() => { this.log.verbose('clearing temporary .desktop'); this.$.utils.rmWithRetries('-rf', this.$.env.paths.desktopTmp.root).then(() => { resolve(); }).catch(e => { reject(e); }); resolve(); }); }); } /** * Makes a temporary copy of .desktop. */ copyDesktopToDesktopTemp() { this.log.verbose('copying .desktop to temporary location'); _shelljs.default.cp('-rf', this.$.env.paths.desktop.root, this.$.env.paths.desktopTmp.root); // Remove test files. _del.default.sync([_path.default.join(this.$.env.paths.desktopTmp.root, '**', '*.test.js')], { force: true }); } /** * Runs babel and uglify over .desktop if requested. */ async transpileAndMinify() { this.log.info('transpiling and uglifying'); const settings = this.$.desktop.getSettings(); const options = 'uglifyOptions' in settings ? settings.uglifyOptions : {}; const uglifyingEnabled = 'uglify' in settings && !!settings.uglify; const preset = (0, _presetEnv.default)({ assertVersion: () => {} }, { targets: { node: '12' } }); const { data: files } = await this.$.utils.readDir(this.$.env.paths.desktopTmp.root); files.forEach(file => { if (file.endsWith('.js')) { let { code } = (0, _core.transformFileSync)(file, { presets: [preset] }); let error; if (settings.env === 'prod' && uglifyingEnabled) { ({ code, error } = _terser.default.minify(code, options)); } if (error) { throw new Error(error); } _fs.default.writeFileSync(file, code); } }); } /** * Moves all the files that should not be packed into asar into a safe location which is the * 'extracted' dir in the electron app. */ async excludeFilesFromArchive() { this.log.info('excluding files from packing'); // Ensure empty `extracted` dir try { await this.$.utils.rmWithRetries('-rf', this.$.env.paths.electronApp.extracted); } catch (e) { throw new Error(e); } _shelljs.default.mkdir(this.$.env.paths.electronApp.extracted); const configs = this.$.desktop.gatherModuleConfigs(); // Move files that should not be asar'ed. configs.forEach(config => { const moduleConfig = config; if ('extract' in moduleConfig) { if (!Array.isArray(moduleConfig.extract)) { moduleConfig.extract = [moduleConfig.extract]; } moduleConfig.extract.forEach(file => { this.log.debug(`excluding ${file} from ${config.name}`); const filePath = _path.default.join(this.$.env.paths.desktopTmp.modules, moduleConfig.dirName, file); const destinationPath = _path.default.join(this.$.env.paths.electronApp.extracted, moduleConfig.dirName); if (!this.$.utils.exists(destinationPath)) { _shelljs.default.mkdir(destinationPath); } _shelljs.default.mv(filePath, destinationPath); }); } }); } } exports.default = ElectronApp; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfcnVudGltZSIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJyZXF1aXJlIiwiX2FzYXIiLCJfYXNzaWduSW4iLCJfbG9kYXNoIiwiX2luc3RhbGxMb2NhbCIsIl9jb3JlIiwiX2NyeXB0byIsIl9kZWwiLCJfcHJlc2V0RW52IiwiX2ZzIiwiX3BhdGgiLCJfc2hlbGxqcyIsIl9zZW12ZXIiLCJfdGVyc2VyIiwiX2xvZyIsIl9lbGVjdHJvbkFwcFNjYWZmb2xkIiwiX2RlcGVuZGVuY2llc01hbmFnZXIiLCJfYmluYXJ5TW9kdWxlc0RldGVjdG9yIiwib2JqIiwiX19lc01vZHVsZSIsImRlZmF1bHQiLCJzaGVsbCIsImNvbmZpZyIsImZhdGFsIiwiRWxlY3Ryb25BcHAiLCJjb25zdHJ1Y3RvciIsIiQiLCJsb2ciLCJMb2ciLCJzY2FmZm9sZCIsIkVsZWN0cm9uQXBwU2NhZmZvbGQiLCJkZXBzTWFuYWdlciIsIkRlcGVuZGVuY2llc01hbmFnZXIiLCJnZXREZWZhdWx0UGFja2FnZUpzb24iLCJkZXBlbmRlbmNpZXMiLCJtZXRlb3JBcHAiLCJwYWNrYWdlSnNvbiIsInZlcnNpb24iLCJjb21wYXRpYmlsaXR5VmVyc2lvbiIsImRlcHJlY3RhdGVkUGx1Z2lucyIsInBhY2tTa2VsZXRvblRvQXNhciIsImV4Y2x1ZGVGcm9tRGVsIiwiaW5mbyIsIlByb21pc2UiLCJyZXNvbHZlIiwiZXh0cmFjdCIsImdldE1vZHVsZXNUb0V4dHJhY3QiLCJkZWJ1ZyIsImZzIiwicmVuYW1lU3luYyIsImVudiIsInBhdGhzIiwiZWxlY3Ryb25BcHAiLCJub2RlTW9kdWxlcyIsInBhdGgiLCJqb2luIiwiYXBwUm9vdCIsImV4dHJhY3RlZCIsImV4dHJhY3RNb2R1bGVzIiwiYXNhciIsImNyZWF0ZVBhY2thZ2UiLCJhcHBBc2FyIiwidGhlbiIsIm12IiwiZm9yRWFjaCIsIm1vZHVsZSIsImNwIiwiZXh0cmFjdGVkTm9kZU1vZHVsZXMiLCJ1dGlscyIsImV4aXN0cyIsImV4dHJhY3RlZE5vZGVNb2R1bGVzQmluIiwiZXhjbHVkZSIsImNvbmNhdCIsImRlbCIsInN5bmMiLCJyb290Iiwic2VwIiwibWFwIiwicGF0aFRvRXhjbHVkZSIsImZvcmNlIiwiZXh0IiwibGVuZ3RoIiwicm0iLCJta2RpclN5bmMiLCJleHRyYWN0QmluIiwiSlNPTiIsInBhcnNlIiwicmVhZEZpbGVTeW5jIiwiZSIsImJpbnMiLCJiaW4iLCJPYmplY3QiLCJrZXlzIiwiZXh0ZW5zaW9uIiwiYmluRmlsZVBhdGgiLCJzeW1saW5rRXhpc3RzIiwiYmluYXJ5TW9kdWxlc0RldGVjdG9yIiwiQmluYXJ5TW9kdWxlRGV0ZWN0b3IiLCJ0b0JlRXh0cmFjdGVkIiwiZGV0ZWN0IiwiZGVza3RvcCIsImdldFNldHRpbmdzIiwiQXJyYXkiLCJpc0FycmF5IiwibWVyZ2UiLCJ2ZXJib3NlIiwiY2FsY3VsYXRlQ29tcGF0aWJpbGl0eVZlcnNpb24iLCJzZXR0aW5ncyIsImRlc2t0b3BIQ1BDb21wYXRpYmlsaXR5VmVyc2lvbiIsIndhcm4iLCJtZDUiLCJjcnlwdG8iLCJjcmVhdGVIYXNoIiwiZ2V0RGVwZW5kZW5jaWVzIiwiZGVwZW5kZW5jaWVzU29ydGVkIiwic29ydCIsImRlcGVuZGVuY3kiLCJtYWluQ29tcGF0aWJpbGl0eVZlcnNpb24iLCJnZXRWZXJzaW9uIiwic3BsaXQiLCJwdXNoIiwiZGVza3RvcENvbXBhdGliaWxpdHlWZXJzaW9uIiwicHJvY2VzcyIsIk1FVEVPUl9ERVNLVE9QX0RFQlVHX0RFU0tUT1BfQ09NUEFUSUJJTElUWV9WRVJTSU9OIiwiTUVURU9SX0RFU0tUT1BfREVCVUciLCJzdHJpbmdpZnkiLCJ1cGRhdGUiLCJkaWdlc3QiLCJpbml0IiwiZWxlY3Ryb24iLCJlbGVjdHJvbkJ1aWxkZXIiLCJleGl0IiwiYnVpbGQiLCJydW4iLCJjaGVjayIsIm9wdGlvbnMiLCJlcnJvciIsInVwZGF0ZUdpdElnbm9yZSIsInJvb3ROYW1lIiwicmVtb3ZlRGVwcmVjYXRlZFBhY2thZ2VzIiwiZW5zdXJlRGVza3RvcEhDUFBhY2thZ2VzIiwibWFrZSIsImZpbGVOYW1lIiwiZGlyTmFtZSIsImV4aXN0c1N5bmMiLCJjb3B5RmlsZVN5bmMiLCJleHBvc2VFbGVjdHJvbk1vZHVsZXMiLCJ1cGRhdGVQYWNrYWdlSnNvbkZpZWxkcyIsInVwZGF0ZURlcGVuZGVuY2llc0xpc3QiLCJoYW5kbGVUZW1wb3JhcnlOb2RlTW9kdWxlcyIsIm5vZGVNb2R1bGVzUmVtb3ZlZCIsImhhbmRsZVN0YXRlT2ZOb2RlTW9kdWxlcyIsInJlYnVpbGREZXBzIiwibGlua05wbVBhY2thZ2VzIiwiaW5zdGFsbExvY2FsTm9kZU1vZHVsZXMiLCJlbnN1cmVNZXRlb3JEZXBlbmRlbmNpZXMiLCJpc1Byb2R1Y3Rpb25CdWlsZCIsImNvcHlEZXNrdG9wVG9EZXNrdG9wVGVtcCIsInVwZGF0ZVNldHRpbmdzSnNvbkZpZWxkcyIsImV4Y2x1ZGVGaWxlc0Zyb21BcmNoaXZlIiwidHJhbnNwaWxlQW5kTWluaWZ5IiwicGFja0Rlc2t0b3BUb0FzYXIiLCJnZXRNZXRlb3JDbGllbnRCdWlsZCIsImV4cG9zZWRNb2R1bGVzIiwicHJlbG9hZCIsIm1vZHVsZXMiLCJyZWR1Y2UiLCJwcmV2IiwicmVwbGFjZSIsIndyaXRlRmlsZVN5bmMiLCJwYWNrYWdlcyIsInBhY2thZ2VzV2l0aFZlcnNpb24iLCJwbHVnaW5zIiwicGx1Z2luIiwibWV0ZW9yRGVwZW5kZW5jaWVzIiwidW5zaGlmdCIsInBhY2thZ2VOYW1lIiwicGFja2FnZXNDb3VudCIsImZpbHRlciIsInZhbHVlIiwiaW5jbHVkZXMiLCJzdWJzdHIiLCJtZXRlb3JNYW5hZ2VyIiwiZW5zdXJlUGFja2FnZXMiLCJFcnJvciIsImlhMzIiLCJybVdpdGhSZXRyaWVzIiwidG1wTm9kZU1vZHVsZXMiLCJwcm9taXNlcyIsImxpbmtQYWNrYWdlcyIsInJ1bk5wbSIsInVuZGVmaW5lZCIsImFsbCIsImVuc3VyZURlcHMiLCJzdGRpbyIsImNoZWNrUGx1Z2luc1ZlcnNpb24iLCJwbHVnaW5zVmVyc2lvbnMiLCJzZXR0aW5nc0pzb24iLCJzY2FmZm9sZFBsdWdpbnNWZXJzaW9uIiwicGx1Z2luTmFtZSIsInNlbXZlciIsImx0IiwiZGVza3RvcERlcGVuZGVuY2llcyIsIm1lcmdlRGVwZW5kZW5jaWVzIiwiZnJvbVNldHRpbmdzIiwiZ2V0UmVtb3RlRGVwZW5kZW5jaWVzIiwibG9jYWxEZXBlbmRlbmNpZXMiLCJnZXRMb2NhbERlcGVuZGVuY2llcyIsImFyY2giLCJfIiwidmFsdWVzIiwibGFzdFJlYnVpbGQiLCJwcmVwYXJlTGFzdFJlYnVpbGRPYmplY3QiLCJnZXRHeXBFbnYiLCJmcmFtZXdvcmtJbmZvIiwicGxhdGZvcm0iLCJpbnN0YWxsZXIiLCJMb2NhbEluc3RhbGxlciIsIm5wbUVudiIsInByb2dyZXNzIiwiaW5zdGFsbCIsImluc3RhbGxPclJlYnVpbGQiLCJhc3NpZ25JbiIsInBhY2thZ2VKc29uRmllbGRzIiwibmFtZSIsInByb2plY3ROYW1lIiwiZ2V0SGFzaFZlcnNpb24iLCJkZXNrdG9wVmVyc2lvbiIsIm1ldGVvckRlc2t0b3BWZXJzaW9uIiwicHJvZERlYnVnIiwiZGVza3RvcFRtcCIsInJlamVjdCIsImRlc2t0b3BBc2FyIiwiY2F0Y2giLCJ1Z2xpZnlPcHRpb25zIiwidWdsaWZ5aW5nRW5hYmxlZCIsInVnbGlmeSIsInByZXNldCIsInByZXNldEVudiIsImFzc2VydFZlcnNpb24iLCJ0YXJnZXRzIiwibm9kZSIsImRhdGEiLCJmaWxlcyIsInJlYWREaXIiLCJmaWxlIiwiZW5kc1dpdGgiLCJjb2RlIiwidHJhbnNmb3JtRmlsZVN5bmMiLCJwcmVzZXRzIiwibWluaWZ5IiwibWtkaXIiLCJjb25maWdzIiwiZ2F0aGVyTW9kdWxlQ29uZmlncyIsIm1vZHVsZUNvbmZpZyIsImZpbGVQYXRoIiwiZGVzdGluYXRpb25QYXRoIiwiZXhwb3J0cyJdLCJzb3VyY2VzIjpbIi4uL2xpYi9lbGVjdHJvbkFwcC5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tdW51c2VkLXZhcnNcbmltcG9ydCByZWdlbmVyYXRvclJ1bnRpbWUgZnJvbSAncmVnZW5lcmF0b3ItcnVudGltZS9ydW50aW1lJztcbmltcG9ydCBhc2FyIGZyb20gJ0BlbGVjdHJvbi9hc2FyJztcbmltcG9ydCBhc3NpZ25JbiBmcm9tICdsb2Rhc2gvYXNzaWduSW4nO1xuaW1wb3J0IF8gZnJvbSAnbG9kYXNoJztcbmltcG9ydCB7IExvY2FsSW5zdGFsbGVyLCBwcm9ncmVzcyB9IGZyb20gJ2luc3RhbGwtbG9jYWwnO1xuaW1wb3J0IHsgdHJhbnNmb3JtRmlsZVN5bmMgfSBmcm9tICdAYmFiZWwvY29yZSc7XG5pbXBvcnQgY3J5cHRvIGZyb20gJ2NyeXB0byc7XG5pbXBvcnQgZGVsIGZyb20gJ2RlbCc7XG5pbXBvcnQgcHJlc2V0RW52IGZyb20gJ0BiYWJlbC9wcmVzZXQtZW52JztcbmltcG9ydCBmcyBmcm9tICdmcyc7XG5pbXBvcnQgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCBzaGVsbCBmcm9tICdzaGVsbGpzJztcbmltcG9ydCBzZW12ZXIgZnJvbSAnc2VtdmVyJztcbmltcG9ydCB1Z2xpZnkgZnJvbSAndGVyc2VyJztcblxuaW1wb3J0IExvZyBmcm9tICcuL2xvZyc7XG5pbXBvcnQgRWxlY3Ryb25BcHBTY2FmZm9sZCBmcm9tICcuL2VsZWN0cm9uQXBwU2NhZmZvbGQnO1xuaW1wb3J0IERlcGVuZGVuY2llc01hbmFnZXIgZnJvbSAnLi9kZXBlbmRlbmNpZXNNYW5hZ2VyJztcbmltcG9ydCBCaW5hcnlNb2R1bGVEZXRlY3RvciBmcm9tICcuL2JpbmFyeU1vZHVsZXNEZXRlY3Rvcic7XG5cbnNoZWxsLmNvbmZpZy5mYXRhbCA9IHRydWU7XG5cbi8qKlxuICogUmVwcmVzZW50cyB0aGUgLmRlc2t0b3AgZGlyIHNjYWZmb2xkLlxuICogQGNsYXNzXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEVsZWN0cm9uQXBwIHtcbiAgICAvKipcbiAgICAgKiBAcGFyYW0ge01ldGVvckRlc2t0b3B9ICQgLSBjb250ZXh0XG4gICAgICogQGNvbnN0cnVjdG9yXG4gICAgICovXG4gICAgY29uc3RydWN0b3IoJCkge1xuICAgICAgICB0aGlzLmxvZyA9IG5ldyBMb2coJ2VsZWN0cm9uQXBwJyk7XG4gICAgICAgIHRoaXMuc2NhZmZvbGQgPSBuZXcgRWxlY3Ryb25BcHBTY2FmZm9sZCgkKTtcbiAgICAgICAgdGhpcy5kZXBzTWFuYWdlciA9IG5ldyBEZXBlbmRlbmNpZXNNYW5hZ2VyKFxuICAgICAgICAgICAgJCxcbiAgICAgICAgICAgIHRoaXMuc2NhZmZvbGQuZ2V0RGVmYXVsdFBhY2thZ2VKc29uKCkuZGVwZW5kZW5jaWVzXG4gICAgICAgICk7XG4gICAgICAgIHRoaXMuJCA9ICQ7XG4gICAgICAgIHRoaXMubWV0ZW9yQXBwID0gdGhpcy4kLm1ldGVvckFwcDtcbiAgICAgICAgdGhpcy5wYWNrYWdlSnNvbiA9IG51bGw7XG4gICAgICAgIHRoaXMudmVyc2lvbiA9IG51bGw7XG4gICAgICAgIHRoaXMuY29tcGF0aWJpbGl0eVZlcnNpb24gPSBudWxsO1xuICAgICAgICB0aGlzLmRlcHJlY3RhdGVkUGx1Z2lucyA9IFsnbWV0ZW9yLWRlc2t0b3AtbG9jYWxzdG9yYWdlJ107XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogTWFrZXMgYW4gYXBwLmFzYXIgZnJvbSB0aGUgc2tlbGV0b24gYXBwLlxuICAgICAqIEBwcm9wZXJ0eSB7QXJyYXl9IGV4Y2x1ZGVGcm9tRGVsIC0gbGlzdCBvZiBwYXRocyB0byBleGNsdWRlIGZyb20gZGVsZXRpbmdcbiAgICAgKiBAcmV0dXJucyB7UHJvbWlzZX1cbiAgICAgKi9cbiAgICBwYWNrU2tlbGV0b25Ub0FzYXIoZXhjbHVkZUZyb21EZWwgPSBbXSkge1xuICAgICAgICB0aGlzLmxvZy5pbmZvKCdwYWNraW5nIHNrZWxldG9uIGFwcCBhbmQgbm9kZV9tb2R1bGVzIHRvIGFzYXIgYXJjaGl2ZScpO1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGV4dHJhY3QgPSB0aGlzLmdldE1vZHVsZXNUb0V4dHJhY3QoKTtcblxuICAgICAgICAgICAgLy8gV2Ugd2FudCB0byBwYWNrIHNrZWxldG9uIGFwcCBhbmQgbm9kZV9tb2R1bGVzIHRvZ2V0aGVyLCBzbyB3ZSBuZWVkIHRvIHRlbXBvcmFyaWx5XG4gICAgICAgICAgICAvLyBtb3ZlIG5vZGVfbW9kdWxlcyB0byBhcHAgZGlyLlxuICAgICAgICAgICAgdGhpcy5sb2cuZGVidWcoJ21vdmluZyBub2RlX21vZHVsZXMgdG8gYXBwIGRpcicpO1xuXG4gICAgICAgICAgICBmcy5yZW5hbWVTeW5jKFxuICAgICAgICAgICAgICAgIHRoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAubm9kZU1vZHVsZXMsXG4gICAgICAgICAgICAgICAgcGF0aC5qb2luKHRoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAuYXBwUm9vdCwgJ25vZGVfbW9kdWxlcycpXG4gICAgICAgICAgICApO1xuXG4gICAgICAgICAgICBsZXQgZXh0cmFjdGVkID0gZmFsc2U7XG4gICAgICAgICAgICBleHRyYWN0ZWQgPSB0aGlzLmV4dHJhY3RNb2R1bGVzKGV4dHJhY3QpO1xuXG4gICAgICAgICAgICB0aGlzLmxvZy5kZWJ1ZygncGFja2luZycpO1xuICAgICAgICAgICAgYXNhci5jcmVhdGVQYWNrYWdlKFxuICAgICAgICAgICAgICAgIHRoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAuYXBwUm9vdCxcbiAgICAgICAgICAgICAgICB0aGlzLiQuZW52LnBhdGhzLmVsZWN0cm9uQXBwLmFwcEFzYXIsXG4gICAgICAgICAgICApLnRoZW4oKCkgPT4ge1xuICAgICAgICAgICAgICAgIC8vIExldHMgbW92ZSB0aGUgbm9kZV9tb2R1bGVzIGJhY2suXG4gICAgICAgICAgICAgICAgdGhpcy5sb2cuZGVidWcoJ21vdmluZyBub2RlX21vZHVsZXMgYmFjayBmcm9tIGFwcCBkaXInKTtcblxuICAgICAgICAgICAgICAgIHNoZWxsLm12KFxuICAgICAgICAgICAgICAgICAgICBwYXRoLmpvaW4odGhpcy4kLmVudi5wYXRocy5lbGVjdHJvbkFwcC5hcHBSb290LCAnbm9kZV9tb2R1bGVzJyksXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAubm9kZU1vZHVsZXNcbiAgICAgICAgICAgICAgICApO1xuXG4gICAgICAgICAgICAgICAgaWYgKGV4dHJhY3RlZCkge1xuICAgICAgICAgICAgICAgICAgICAvLyBXZSBuZWVkIHRvIGNyZWF0ZSBhIGZ1bGwgbm9kZSBtb2R1bGVzIGJhY2suIEluIG90aGVyIHdvcmRzIHdlIHdhbnRcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhlIGV4dHJhY3RlZCBtb2R1bGVzIGJhY2suXG4gICAgICAgICAgICAgICAgICAgIGV4dHJhY3QuZm9yRWFjaChtb2R1bGUgPT4gc2hlbGwuY3AoXG4gICAgICAgICAgICAgICAgICAgICAgICAnLXJmJyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHBhdGguam9pbih0aGlzLiQuZW52LnBhdGhzLmVsZWN0cm9uQXBwLmV4dHJhY3RlZE5vZGVNb2R1bGVzLCBtb2R1bGUpLFxuICAgICAgICAgICAgICAgICAgICAgICAgcGF0aC5qb2luKHRoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAubm9kZU1vZHVsZXMsIG1vZHVsZSlcbiAgICAgICAgICAgICAgICAgICAgKSk7XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gR2V0IHRoZSAuYmluIGJhY2suXG4gICAgICAgICAgICAgICAgICAgIGlmICh0aGlzLiQudXRpbHMuZXhpc3RzKFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy4kLmVudi5wYXRocy5lbGVjdHJvbkFwcC5leHRyYWN0ZWROb2RlTW9kdWxlc0JpblxuICAgICAgICAgICAgICAgICAgICApKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzaGVsbC5jcChcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXRoLmpvaW4odGhpcy4kLmVudi5wYXRocy5lbGVjdHJvbkFwcC5leHRyYWN0ZWROb2RlTW9kdWxlc0JpbiwgJyonKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXRoLmpvaW4odGhpcy4kLmVudi5wYXRocy5lbGVjdHJvbkFwcC5ub2RlTW9kdWxlcywgJy5iaW4nKVxuICAgICAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHRoaXMubG9nLmRlYnVnKCdkZWxldGluZyBzb3VyY2UgZmlsZXMnKTtcbiAgICAgICAgICAgICAgICBjb25zdCBleGNsdWRlID0gW3RoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAubm9kZU1vZHVsZXNdLmNvbmNhdChcbiAgICAgICAgICAgICAgICAgICAgW1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy4kLmVudi5wYXRocy5lbGVjdHJvbkFwcC5hcHBBc2FyLFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy4kLmVudi5wYXRocy5lbGVjdHJvbkFwcC5wYWNrYWdlSnNvblxuICAgICAgICAgICAgICAgICAgICBdLFxuICAgICAgICAgICAgICAgICAgICBleGNsdWRlRnJvbURlbFxuICAgICAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgICAgICBkZWwuc3luYyhcbiAgICAgICAgICAgICAgICAgICAgW2Ake3RoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAucm9vdH0ke3BhdGguc2VwfSpgXS5jb25jYXQoXG4gICAgICAgICAgICAgICAgICAgICAgICBleGNsdWRlLm1hcChwYXRoVG9FeGNsdWRlID0+IGAhJHtwYXRoVG9FeGNsdWRlfWApXG4gICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgIHsgZm9yY2U6IHRydWUgfVxuICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE1vdmVzIHNwZWNpZmllZCBub2RlIG1vZHVsZXMgdG8gYSBzZXBhcmF0ZSBkaXJlY3RvcnkuXG4gICAgICogQHBhcmFtIHtBcnJheX0gZXh0cmFjdFxuICAgICAqIEByZXR1cm5zIHtib29sZWFufVxuICAgICAqL1xuICAgIGV4dHJhY3RNb2R1bGVzKGV4dHJhY3QpIHtcbiAgICAgICAgY29uc3QgZXh0ID0gWycuanMnLCAnLmJhdCcsICcuc2gnLCAnLmNtZCcsICcnXTtcblxuICAgICAgICBpZiAoZXh0cmFjdC5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBpZiAodGhpcy4kLnV0aWxzLmV4aXN0cyh0aGlzLiQuZW52LnBhdGhzLmVsZWN0cm9uQXBwLmV4dHJhY3RlZE5vZGVNb2R1bGVzKSkge1xuICAgICAgICAgICAgICAgIHNoZWxsLnJtKCctcmYnLCB0aGlzLiQuZW52LnBhdGhzLmVsZWN0cm9uQXBwLmV4dHJhY3RlZE5vZGVNb2R1bGVzKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGZzLm1rZGlyU3luYyh0aGlzLiQuZW52LnBhdGhzLmVsZWN0cm9uQXBwLmV4dHJhY3RlZE5vZGVNb2R1bGVzKTtcbiAgICAgICAgICAgIGZzLm1rZGlyU3luYyh0aGlzLiQuZW52LnBhdGhzLmVsZWN0cm9uQXBwLmV4dHJhY3RlZE5vZGVNb2R1bGVzQmluKTtcblxuICAgICAgICAgICAgZXh0cmFjdC5mb3JFYWNoKChtb2R1bGUpID0+IHtcbiAgICAgICAgICAgICAgICBmcy5yZW5hbWVTeW5jKFxuICAgICAgICAgICAgICAgICAgICBwYXRoLmpvaW4odGhpcy4kLmVudi5wYXRocy5lbGVjdHJvbkFwcC5hcHBSb290LCAnbm9kZV9tb2R1bGVzJywgbW9kdWxlKSxcbiAgICAgICAgICAgICAgICAgICAgcGF0aC5qb2luKHRoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAuZXh0cmFjdGVkTm9kZU1vZHVsZXMsIG1vZHVsZSksXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAvLyBNb3ZlIGJpbnMuXG4gICAgICAgICAgICAgICAgdGhpcy5leHRyYWN0QmluKG1vZHVsZSwgZXh0KTtcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRXh0cmFjdHMgdGhlIGJpbiBmaWxlcyBhc3NvY2lhdGVkIHdpdGggYSBjZXJ0YWluIG5vZGUgbW9kdWxlcy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBtb2R1bGVcbiAgICAgKiBAcGFyYW0gZXh0XG4gICAgICovXG4gICAgZXh0cmFjdEJpbihtb2R1bGUsIGV4dCkge1xuICAgICAgICBsZXQgcGFja2FnZUpzb247XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBwYWNrYWdlSnNvbiA9IEpTT04ucGFyc2UoXG4gICAgICAgICAgICAgICAgZnMucmVhZEZpbGVTeW5jKFxuICAgICAgICAgICAgICAgICAgICBwYXRoLmpvaW4oXG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLiQuZW52LnBhdGhzLmVsZWN0cm9uQXBwLmV4dHJhY3RlZE5vZGVNb2R1bGVzLCBtb2R1bGUsICdwYWNrYWdlLmpzb24nXG4gICAgICAgICAgICAgICAgICAgICksXG4gICAgICAgICAgICAgICAgICAgICd1dGY4J1xuICAgICAgICAgICAgICAgIClcbiAgICAgICAgICAgICk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHBhY2thZ2VKc29uID0ge307XG4gICAgICAgIH1cblxuXG4gICAgICAgIGNvbnN0IGJpbnMgPSAoJ2JpbicgaW4gcGFja2FnZUpzb24gJiYgdHlwZW9mIHBhY2thZ2VKc29uLmJpbiA9PT0gJ29iamVjdCcpID8gT2JqZWN0LmtleXMocGFja2FnZUpzb24uYmluKSA6IFtdO1xuXG4gICAgICAgIGlmIChiaW5zLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIGJpbnMuZm9yRWFjaCgoYmluKSA9PiB7XG4gICAgICAgICAgICAgICAgZXh0LmZvckVhY2goKGV4dGVuc2lvbikgPT4ge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBiaW5GaWxlUGF0aCA9IHBhdGguam9pbihcbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAuYXBwUm9vdCxcbiAgICAgICAgICAgICAgICAgICAgICAgICdub2RlX21vZHVsZXMnLFxuICAgICAgICAgICAgICAgICAgICAgICAgJy5iaW4nLFxuICAgICAgICAgICAgICAgICAgICAgICAgYCR7YmlufSR7ZXh0ZW5zaW9ufWBcbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXMuJC51dGlscy5leGlzdHMoYmluRmlsZVBhdGgpIHx8XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLiQudXRpbHMuc3ltbGlua0V4aXN0cyhiaW5GaWxlUGF0aClcbiAgICAgICAgICAgICAgICAgICAgKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBmcy5yZW5hbWVTeW5jKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJpbkZpbGVQYXRoLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdGguam9pbihcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy4kLmVudi5wYXRocy5lbGVjdHJvbkFwcC5leHRyYWN0ZWROb2RlTW9kdWxlc0JpbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYCR7YmlufSR7ZXh0ZW5zaW9ufWBcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICApXG4gICAgICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE1lcmdlcyB0aGUgYGV4dHJhY3RgIGZpZWxkIHdpdGggYXV0b21hdGljYWxseSBkZXRlY3RlZCBtb2R1bGVzLlxuICAgICAqL1xuICAgIGdldE1vZHVsZXNUb0V4dHJhY3QoKSB7XG4gICAgICAgIGNvbnN0IGJpbmFyeU1vZHVsZXNEZXRlY3RvciA9XG4gICAgICAgICAgICBuZXcgQmluYXJ5TW9kdWxlRGV0ZWN0b3IodGhpcy4kLmVudi5wYXRocy5lbGVjdHJvbkFwcC5ub2RlTW9kdWxlcyk7XG4gICAgICAgIGNvbnN0IHRvQmVFeHRyYWN0ZWQgPSBiaW5hcnlNb2R1bGVzRGV0ZWN0b3IuZGV0ZWN0KCk7XG5cbiAgICAgICAgbGV0IHsgZXh0cmFjdCB9ID0gdGhpcy4kLmRlc2t0b3AuZ2V0U2V0dGluZ3MoKTtcblxuICAgICAgICBpZiAoIUFycmF5LmlzQXJyYXkoZXh0cmFjdCkpIHtcbiAgICAgICAgICAgIGV4dHJhY3QgPSBbXTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IG1lcmdlID0ge307XG4gICAgICAgIHRvQmVFeHRyYWN0ZWQuY29uY2F0KGV4dHJhY3QpLmZvckVhY2goKG1vZHVsZSkgPT4ge1xuICAgICAgICAgICAgbWVyZ2VbbW9kdWxlXSA9IHRydWU7XG4gICAgICAgIH0pO1xuICAgICAgICBleHRyYWN0ID0gT2JqZWN0LmtleXMobWVyZ2UpO1xuICAgICAgICBpZiAoZXh0cmFjdC5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICB0aGlzLmxvZy52ZXJib3NlKGByZXN1bHRhbnQgbW9kdWxlcyB0byBleHRyYWN0IGxpc3QgaXM6ICR7ZXh0cmFjdC5qb2luKCcsICcpfWApO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBleHRyYWN0O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENhbGN1bGF0ZXMgYSBtZDUgZnJvbSBhbGwgZGVwZW5kZW5jaWVzLlxuICAgICAqL1xuICAgIGNhbGN1bGF0ZUNvbXBhdGliaWxpdHlWZXJzaW9uKCkge1xuICAgICAgICB0aGlzLmxvZy52ZXJib3NlKCdjYWxjdWxhdGluZyBjb21wYXRpYmlsaXR5IHZlcnNpb24nKTtcbiAgICAgICAgY29uc3Qgc2V0dGluZ3MgPSB0aGlzLiQuZGVza3RvcC5nZXRTZXR0aW5ncygpO1xuXG4gICAgICAgIGlmICgoJ2Rlc2t0b3BIQ1BDb21wYXRpYmlsaXR5VmVyc2lvbicgaW4gc2V0dGluZ3MpKSB7XG4gICAgICAgICAgICB0aGlzLmNvbXBhdGliaWxpdHlWZXJzaW9uID0gYCR7c2V0dGluZ3MuZGVza3RvcEhDUENvbXBhdGliaWxpdHlWZXJzaW9ufWA7XG4gICAgICAgICAgICB0aGlzLmxvZy53YXJuKGBjb21wYXRpYmlsaXR5IHZlcnNpb24gb3ZlcnJpZGRlbiB0byAke3RoaXMuY29tcGF0aWJpbGl0eVZlcnNpb259YCk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBtZDUgPSBjcnlwdG8uY3JlYXRlSGFzaCgnbWQ1Jyk7XG4gICAgICAgIGxldCBkZXBlbmRlbmNpZXMgPSB0aGlzLmRlcHNNYW5hZ2VyLmdldERlcGVuZGVuY2llcygpO1xuICAgICAgICBjb25zdCBkZXBlbmRlbmNpZXNTb3J0ZWQgPSBPYmplY3Qua2V5cyhkZXBlbmRlbmNpZXMpLnNvcnQoKTtcbiAgICAgICAgZGVwZW5kZW5jaWVzID0gZGVwZW5kZW5jaWVzU29ydGVkLm1hcChkZXBlbmRlbmN5ID0+XG4gICAgICAgICAgICBgJHtkZXBlbmRlbmN5fToke2RlcGVuZGVuY2llc1tkZXBlbmRlbmN5XX1gKTtcbiAgICAgICAgY29uc3QgbWFpbkNvbXBhdGliaWxpdHlWZXJzaW9uID0gdGhpcy4kLmdldFZlcnNpb24oKS5zcGxpdCgnLicpO1xuICAgICAgICB0aGlzLmxvZy5kZWJ1ZygnbWV0ZW9yLWRlc2t0b3AgY29tcGF0aWJpbGl0eSB2ZXJzaW9uIGlzICcsXG4gICAgICAgICAgICBgJHttYWluQ29tcGF0aWJpbGl0eVZlcnNpb25bMF19YCk7XG4gICAgICAgIGRlcGVuZGVuY2llcy5wdXNoKFxuICAgICAgICAgICAgYG1ldGVvci1kZXNrdG9wOiR7bWFpbkNvbXBhdGliaWxpdHlWZXJzaW9uWzBdfWBcbiAgICAgICAgKTtcblxuICAgICAgICBjb25zdCBkZXNrdG9wQ29tcGF0aWJpbGl0eVZlcnNpb24gPSBzZXR0aW5ncy52ZXJzaW9uLnNwbGl0KCcuJylbMF07XG4gICAgICAgIHRoaXMubG9nLmRlYnVnKCcuZGVza3RvcCBjb21wYXRpYmlsaXR5IHZlcnNpb24gaXMgJywgZGVza3RvcENvbXBhdGliaWxpdHlWZXJzaW9uKTtcbiAgICAgICAgZGVwZW5kZW5jaWVzLnB1c2goXG4gICAgICAgICAgICBgZGVza3RvcC1hcHA6JHtkZXNrdG9wQ29tcGF0aWJpbGl0eVZlcnNpb259YFxuICAgICAgICApO1xuXG4gICAgICAgIGlmIChwcm9jZXNzLmVudi5NRVRFT1JfREVTS1RPUF9ERUJVR19ERVNLVE9QX0NPTVBBVElCSUxJVFlfVkVSU0lPTiB8fFxuICAgICAgICAgICAgcHJvY2Vzcy5lbnYuTUVURU9SX0RFU0tUT1BfREVCVUdcbiAgICAgICAgKSB7XG4gICAgICAgICAgICB0aGlzLmxvZy5kZWJ1ZyhgY29tcGF0aWJpbGl0eSB2ZXJzaW9uIGNhbGN1bGF0ZWQgZnJvbSAke0pTT04uc3RyaW5naWZ5KGRlcGVuZGVuY2llcyl9YCk7XG4gICAgICAgIH1cblxuICAgICAgICBtZDUudXBkYXRlKEpTT04uc3RyaW5naWZ5KGRlcGVuZGVuY2llcykpO1xuXG4gICAgICAgIHRoaXMuY29tcGF0aWJpbGl0eVZlcnNpb24gPSBtZDUuZGlnZXN0KCdoZXgnKTtcbiAgICB9XG5cbiAgICBhc3luYyBpbml0KCkge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgYXdhaXQgdGhpcy4kLmVsZWN0cm9uLmluaXQoKTtcbiAgICAgICAgICAgIGF3YWl0IHRoaXMuJC5lbGVjdHJvbkJ1aWxkZXIuaW5pdCgpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICB0aGlzLmxvZy53YXJuKCdlcnJvciBvY2N1cnJlZCB3aGlsZSBpbml0aWFsaXNpbmcgZWxlY3Ryb24gYW5kIGVsZWN0cm9uLWJ1aWxkZXIgaW50ZWdyYXRpb24nLCBlKTtcbiAgICAgICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJ1bnMgYWxsIG5lY2Vzc2FyeSB0YXNrcyB0byBidWlsZCB0aGUgZGVza3RvcGlmaWVkIGFwcC5cbiAgICAgKi9cbiAgICBhc3luYyBidWlsZChydW4gPSBmYWxzZSkge1xuICAgICAgICAvLyBUT0RPOiByZWZhY3RvciB0byBhIHRhc2sgcnVubmVyXG4gICAgICAgIHRoaXMubG9nLmluZm8oJ3NjYWZmb2xkaW5nJyk7XG5cbiAgICAgICAgaWYgKCF0aGlzLiQuZGVza3RvcC5jaGVjaygpKSB7XG4gICAgICAgICAgICBpZiAoIXRoaXMuJC5lbnYub3B0aW9ucy5zY2FmZm9sZCkge1xuICAgICAgICAgICAgICAgIHRoaXMubG9nLmVycm9yKCdzZWVtcyB0aGF0IHlvdSBkbyBub3QgaGF2ZSBhIC5kZXNrdG9wIGRpciBpbiB5b3VyIHByb2plY3Qgb3IgaXQgaXMnICtcbiAgICAgICAgICAgICAgICAgICAgJyBjb3JydXB0ZWQuIFJ1biBcXCducG0gcnVuIGRlc2t0b3AgLS0gaW5pdFxcJyB0byBnZXQgYSBuZXcgb25lLicpO1xuICAgICAgICAgICAgICAgIC8vIERvIG5vdCBmYWlsLCBzbyB0aGF0IG5wbSB3aWxsIG5vdCBwcmludCBoaXMgZXJyb3Igc3R1ZmYgdG8gY29uc29sZS5cbiAgICAgICAgICAgICAgICBwcm9jZXNzLmV4aXQoMCk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHRoaXMuJC5kZXNrdG9wLnNjYWZmb2xkKCk7XG4gICAgICAgICAgICAgICAgdGhpcy4kLm1ldGVvckFwcC51cGRhdGVHaXRJZ25vcmUoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGF3YWl0IHRoaXMuaW5pdCgpO1xuXG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHRoaXMuJC5tZXRlb3JBcHAudXBkYXRlR2l0SWdub3JlKCk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHRoaXMubG9nLndhcm4oYGVycm9yIG9jY3VycmVkIHdoaWxlIGFkZGluZyAke3RoaXMuJC5lbnYucGF0aHMuZWxlY3Ryb25BcHAucm9vdE5hbWV9YCArXG4gICAgICAgICAgICAgICAgJ3RvIC5naXRpZ25vcmU6ICcsIGUpO1xuICAgICAgICB9XG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGF3YWl0IHRoaXMuJC5tZXRlb3JBcHAucmVtb3ZlRGVwcmVjYXRlZFBhY2thZ2VzKCk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHRoaXMubG9nLmVycm9yKCdlcnJvciB3aGlsZSByZW1vdmluZyBkZXByZWNhdGVkIHBhY2thZ2VzOiAnLCBlKTtcbiAgICAgICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLiQubWV0ZW9yQXBwLmVuc3VyZURlc2t0b3BIQ1BQYWNrYWdlcygpO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICB0aGlzLmxvZy5lcnJvcignZXJyb3Igd2hpbGUgY2hlY2tpbmcgZm9yIHJlcXVpcmVkIHBhY2thZ2VzOiAnLCBlKTtcbiAgICAgICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBhd2FpdCB0aGlzLnNjYWZmb2xkLm1ha2UoKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgdGhpcy5sb2cuZXJyb3IoJ2Vycm9yIHdoaWxlIHNjYWZmb2xkaW5nOiAnLCBlKTtcbiAgICAgICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCBmaWxlTmFtZSA9ICcubnBtcmMnO1xuICAgICAgICAgICAgY29uc3QgZGlyTmFtZSA9ICcubWV0ZW9yL2Rlc2t0b3AtYnVpbGQnO1xuICAgICAgICAgICAgaWYgKGZzLmV4aXN0c1N5bmMoZGlyTmFtZSkgJiYgZnMuZXhpc3RzU3luYyhmaWxlTmFtZSkpIHtcbiAgICAgICAgICAgICAgICBmcy5jb3B5RmlsZVN5bmMoZmlsZU5hbWUsIGAke2Rpck5hbWV9LyR7ZmlsZU5hbWV9YCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHRoaXMubG9nLndhcm4oJ2Vycm9yIHdoaWxlIGNvcHlpbmcgLm5wbXJjJywgZSk7XG4gICAgICAgIH1cblxuICAgICAgICB0cnkge1xuICAgICAgICAgICAgYXdhaXQgdGhpcy5leHBvc2VFbGVjdHJvbk1vZHVsZXMoKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgdGhpcy5sb2cuZXJyb3IoJ2Vycm9yIHdoaWxlIGV4cG9zaW5nIGVsZWN0cm9uIG1vZHVsZXM6ICcsIGUpO1xuICAgICAgICAgICAgcHJvY2Vzcy5leGl0KDEpO1xuICAgICAgICB9XG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIHRoaXMudXBkYXRlUGFja2FnZUpzb25GaWVsZHMoKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgdGhpcy5sb2cuZXJyb3IoJ2Vycm9yIHdoaWxlIHVwZGF0aW5nIHBhY2thZ2UuanNvbjogJywgZSk7XG4gICAgICAgIH1cblxuICAgICAgICB0cnkge1xuICAgICAgICAgICAgdGhpcy51cGRhdGVEZXBlbmRlbmNpZXNMaXN0KCk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHRoaXMubG9nLmVycm9yKCdlcnJvciB3aGlsZSBtZXJnaW5nIGRlcGVuZGVuY2llcyBsaXN0OiAnLCBlKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICB0aGlzLmNhbGN1bGF0ZUNvbXBhdGliaWxpdHlWZXJzaW9uKCk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHRoaXMubG9nLmVycm9yKCdlcnJvciB3aGlsZSBjYWxjdWxhdGluZyBjb21wYXRpYmlsaXR5IHZlcnNpb246ICcsIGUpO1xuICAgICAgICAgICAgcHJvY2Vzcy5leGl0KDEpO1xuICAgICAgICB9XG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGF3YWl0IHRoaXMuaGFuZGxlVGVtcG9yYXJ5Tm9kZU1vZHVsZXMoKTtcbiAgICAgICAgfSBjYX