appium-ios-simulator
Version:
iOS Simulator interface for Appium.
567 lines (460 loc) • 37.6 kB
JavaScript
;
var _get = require('babel-runtime/helpers/get')['default'];
var _inherits = require('babel-runtime/helpers/inherits')['default'];
var _createClass = require('babel-runtime/helpers/create-class')['default'];
var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
var _regeneratorRuntime = require('babel-runtime/regenerator')['default'];
var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
Object.defineProperty(exports, '__esModule', {
value: true
});
var _simulatorXcode6 = require('./simulator-xcode-6');
var _simulatorXcode7 = require('./simulator-xcode-7');
var _simulatorXcode72 = _interopRequireDefault(_simulatorXcode7);
var _logger = require('./logger');
var _logger2 = _interopRequireDefault(_logger);
var _asyncbox = require('asyncbox');
var _teen_process = require('teen_process');
var _nodeSimctl = require('node-simctl');
// these sims are sloooooooow
var STARTUP_TIMEOUT = 120 * 1000;
var SAFARI_STARTUP_TIMEOUT = 25 * 1000;
var UI_CLIENT_STARTUP_TIMEOUT = 5 * 1000;
var SPRINGBOARD_BUNDLE_ID = 'com.apple.springboard';
var MOBILE_SAFARI_BUNDLE_ID = 'com.apple.mobilesafari';
var UI_CLIENT_BUNDLE_ID = 'com.apple.iphonesimulator';
var PROCESS_LAUNCH_OK_PATTERN = function PROCESS_LAUNCH_OK_PATTERN(bundleId) {
return new RegExp(bundleId.replace('.', '\\.') + ':\\s+\\d+');
};
var SimulatorXcode8 = (function (_SimulatorXcode7) {
_inherits(SimulatorXcode8, _SimulatorXcode7);
function SimulatorXcode8(udid, xcodeVersion) {
_classCallCheck(this, SimulatorXcode8);
_get(Object.getPrototypeOf(SimulatorXcode8.prototype), 'constructor', this).call(this, udid, xcodeVersion);
// list of files to check for when seeing if a simulator is "fresh"
// (meaning it has never been booted).
// If these files are present, we assume it's been successfully booted
this.isFreshFiles = ['Library/Cookies', 'Library/Preferences/.GlobalPreferences.plist', 'Library/Preferences/com.apple.springboard.plist', 'var/run/syslog.pid'];
}
/**
* @return {string} Bundle identifier of Simulator UI client.
*/
_createClass(SimulatorXcode8, [{
key: 'isUIClientRunning',
/**
* Check the state of Simulator UI client.
* @Override
*
* @return {boolean} True of if UI client is running or false otherwise.
*/
value: function isUIClientRunning() {
var args, _ref, stdout, count;
return _regeneratorRuntime.async(function isUIClientRunning$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
args = ['-e', 'tell application "System Events" to count processes whose bundle identifier is "' + this.uiClientBundleId + '"'];
context$2$0.next = 3;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('osascript', args));
case 3:
_ref = context$2$0.sent;
stdout = _ref.stdout;
count = parseInt(stdout, 10);
if (isNaN(count)) {
_logger2['default'].errorAndThrow('Cannot parse the count of running Simulator UI client instances from \'osascript ' + args + '\' output: ' + stdout);
}
_logger2['default'].debug('The count of running Simulator UI client instances is ' + count);
return context$2$0.abrupt('return', count >= 1);
case 9:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Kill the UI client if it is running.
*
* @param {boolean} force - Set it to true to send SIGKILL signal to Simulator process.
* SIGTERM will be sent by default.
* @return {boolean} True if the UI client was successfully killed or false
* if it is not running.
*/
}, {
key: 'killUIClient',
value: function killUIClient() {
var force = arguments.length <= 0 || arguments[0] === undefined ? false : arguments[0];
var osascriptArgs, _ref2, stdout, killArgs;
return _regeneratorRuntime.async(function killUIClient$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
osascriptArgs = ['-e', 'tell application "System Events" to unix id of processes whose bundle identifier is "' + this.uiClientBundleId + '"'];
context$2$0.next = 3;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('osascript', osascriptArgs));
case 3:
_ref2 = context$2$0.sent;
stdout = _ref2.stdout;
if (stdout.trim().length) {
context$2$0.next = 7;
break;
}
return context$2$0.abrupt('return', false);
case 7:
killArgs = force ? ['-9', stdout.trim()] : [stdout.trim()];
context$2$0.next = 10;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('kill', killArgs));
case 10:
return context$2$0.abrupt('return', true);
case 11:
case 'end':
return context$2$0.stop();
}
}, null, this);
}
/**
* Start the Simulator UI client with the given arguments
* and wait until it is running.
* @override
*/
}, {
key: 'startUIClient',
value: function startUIClient() {
var opts = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
return _regeneratorRuntime.async(function startUIClient$(context$2$0) {
var _this = this;
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(_get(Object.getPrototypeOf(SimulatorXcode8.prototype), 'startUIClient', this).call(this, opts));
case 2:
context$2$0.prev = 2;
context$2$0.next = 5;
return _regeneratorRuntime.awrap((0, _asyncbox.waitForCondition)(function callee$2$0() {
return _regeneratorRuntime.async(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
context$3$0.next = 2;
return _regeneratorRuntime.awrap(this.isUIClientRunning());
case 2:
return context$3$0.abrupt('return', context$3$0.sent);
case 3:
case 'end':
return context$3$0.stop();
}
}, null, _this);
}, { waitMs: UI_CLIENT_STARTUP_TIMEOUT, intervalMs: 300 }));
case 5:
context$2$0.next = 10;
break;
case 7:
context$2$0.prev = 7;
context$2$0.t0 = context$2$0['catch'](2);
_logger2['default'].warn('The Simulator UI client is not running after ' + UI_CLIENT_STARTUP_TIMEOUT + ' ms timeout');
case 10:
case 'end':
return context$2$0.stop();
}
}, null, this, [[2, 7]]);
}
/**
* Verify whether the particular application is installed on Simulator.
* @override
*
* @param {string} bundleId - The bundle id of the application to be checked.
* @return {boolean} True if the given application is installed.
*/
}, {
key: 'isAppInstalled',
value: function isAppInstalled(bundleId) {
var appContainer, info;
return _regeneratorRuntime.async(function isAppInstalled$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.prev = 0;
context$2$0.next = 3;
return _regeneratorRuntime.awrap((0, _nodeSimctl.getAppContainer)(this.udid, bundleId, false));
case 3:
appContainer = context$2$0.sent;
return context$2$0.abrupt('return', appContainer.endsWith('.app'));
case 7:
context$2$0.prev = 7;
context$2$0.t0 = context$2$0['catch'](0);
context$2$0.prev = 9;
context$2$0.next = 12;
return _regeneratorRuntime.awrap((0, _nodeSimctl.appInfo)(this.udid, bundleId));
case 12:
info = context$2$0.sent;
return context$2$0.abrupt('return', info.indexOf('ApplicationType') !== -1);
case 16:
context$2$0.prev = 16;
context$2$0.t1 = context$2$0['catch'](9);
return context$2$0.abrupt('return', false);
case 19:
case 'end':
return context$2$0.stop();
}
}, null, this, [[0, 7], [9, 16]]);
}
/**
* @return {string} Application bundle id, which signals that Simulator booting is
* competed if it is running.
*/
}, {
key: 'waitForBoot',
/**
* Verify whether the Simulator booting is completed and/or wait for it
* until the timeout expires.
* @override
*
* @param {number} startupTimeout - the number of milliseconds to wait until booting is completed.
* @emits BOOT_COMPLETED_EVENT if the current Simulator is ready to accept simctl commands, like 'install'.
*/
value: function waitForBoot(startupTimeout) {
var startupTimestamp, lastError;
return _regeneratorRuntime.async(function waitForBoot$(context$2$0) {
var _this3 = this;
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
startupTimestamp = process.hrtime();
lastError = null;
context$2$0.prev = 2;
context$2$0.next = 5;
return _regeneratorRuntime.awrap((function callee$2$0() {
var isOnBootCompletedEmitted;
return _regeneratorRuntime.async(function callee$2$0$(context$3$0) {
var _this2 = this;
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
isOnBootCompletedEmitted = false;
context$3$0.next = 3;
return _regeneratorRuntime.awrap((0, _asyncbox.waitForCondition)(function callee$3$0() {
var _ref3,
// 'springboard' process should be the last one to start after boot
// 'simctl launch' will block until this process is running or fail if booting is still in progress
stdout;
return _regeneratorRuntime.async(function callee$3$0$(context$4$0) {
while (1) switch (context$4$0.prev = context$4$0.next) {
case 0:
context$4$0.prev = 0;
context$4$0.next = 3;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('xcrun', ['simctl', 'launch', this.udid, this.startupPollBundleId]));
case 3:
_ref3 = context$4$0.sent;
stdout = _ref3.stdout;
if (!PROCESS_LAUNCH_OK_PATTERN(this.startupPollBundleId).test(stdout)) {
context$4$0.next = 8;
break;
}
if (!isOnBootCompletedEmitted) {
isOnBootCompletedEmitted = true;
this.emit(_simulatorXcode6.BOOT_COMPLETED_EVENT);
}
return context$4$0.abrupt('return', true);
case 8:
context$4$0.next = 13;
break;
case 10:
context$4$0.prev = 10;
context$4$0.t0 = context$4$0['catch'](0);
lastError = context$4$0.t0.stderr || context$4$0.t0.message;
case 13:
return context$4$0.abrupt('return', false);
case 14:
case 'end':
return context$4$0.stop();
}
}, null, _this2, [[0, 10]]);
}, { waitMs: startupTimeout, intervalMs: 1500 }));
case 3:
case 'end':
return context$3$0.stop();
}
}, null, _this3);
})());
case 5:
context$2$0.next = 10;
break;
case 7:
context$2$0.prev = 7;
context$2$0.t0 = context$2$0['catch'](2);
_logger2['default'].errorAndThrow('Simulator is not booted after ' + process.hrtime(startupTimestamp)[0] + ' seconds ' + ('because of: ' + (lastError || 'an unknown error')));
case 10:
case 'end':
return context$2$0.stop();
}
}, null, this, [[2, 7]]);
}
/**
* Open the given URL in mobile Safari browser.
* The browser will be started automatically if it is not running.
* @override
*
* @param {string} url - The URL to be opened.
*/
}, {
key: 'openUrl',
value: function openUrl(url) {
var launchTimestamp, lastError;
return _regeneratorRuntime.async(function openUrl$(context$2$0) {
var _this4 = this;
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.next = 2;
return _regeneratorRuntime.awrap(this.isRunning());
case 2:
if (context$2$0.sent) {
context$2$0.next = 4;
break;
}
throw new Error('Tried to open ' + url + ', but Simulator is not in Booted state');
case 4:
launchTimestamp = process.hrtime();
lastError = null;
context$2$0.prev = 6;
context$2$0.next = 9;
return _regeneratorRuntime.awrap((0, _asyncbox.waitForCondition)(function callee$2$0() {
var _ref4,
// This is to make sure Safari is already running
stdout;
return _regeneratorRuntime.async(function callee$2$0$(context$3$0) {
while (1) switch (context$3$0.prev = context$3$0.next) {
case 0:
context$3$0.prev = 0;
context$3$0.next = 3;
return _regeneratorRuntime.awrap((0, _teen_process.exec)('xcrun', ['simctl', 'launch', this.udid, MOBILE_SAFARI_BUNDLE_ID]));
case 3:
_ref4 = context$3$0.sent;
stdout = _ref4.stdout;
if (!PROCESS_LAUNCH_OK_PATTERN(MOBILE_SAFARI_BUNDLE_ID).test(stdout)) {
context$3$0.next = 9;
break;
}
context$3$0.next = 8;
return _regeneratorRuntime.awrap((0, _nodeSimctl.openUrl)(this.udid, url));
case 8:
return context$3$0.abrupt('return', true);
case 9:
context$3$0.next = 15;
break;
case 11:
context$3$0.prev = 11;
context$3$0.t0 = context$3$0['catch'](0);
_logger2['default'].error('Failed to open \'' + url + '\' in Safari. Retrying...');
lastError = context$3$0.t0.stderr || context$3$0.t0.message;
case 15:
return context$3$0.abrupt('return', false);
case 16:
case 'end':
return context$3$0.stop();
}
}, null, _this4, [[0, 11]]);
}, { waitMs: SAFARI_STARTUP_TIMEOUT, intervalMs: 500 }));
case 9:
context$2$0.next = 14;
break;
case 11:
context$2$0.prev = 11;
context$2$0.t0 = context$2$0['catch'](6);
_logger2['default'].errorAndThrow('Safari cannot open \'' + url + '\' after ' + process.hrtime(launchTimestamp)[0] + ' seconds ' + ('because of: ' + (lastError || 'an unknown error')));
case 14:
_logger2['default'].debug('Safari has successfully opened \'' + url + '\' in ' + process.hrtime(launchTimestamp)[0] + ' seconds');
case 15:
case 'end':
return context$2$0.stop();
}
}, null, this, [[6, 11]]);
}
/**
* Clean up the directories for mobile Safari.
* @override
*
* @param {boolean} keepPrefs - Whether to keep Safari preferences from being deleted.
*/
}, {
key: 'cleanSafari',
value: function cleanSafari() {
var keepPrefs = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
return _regeneratorRuntime.async(function cleanSafari$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.prev = 0;
context$2$0.next = 3;
return _regeneratorRuntime.awrap((0, _nodeSimctl.terminate)(this.udid, MOBILE_SAFARI_BUNDLE_ID));
case 3:
context$2$0.next = 7;
break;
case 5:
context$2$0.prev = 5;
context$2$0.t0 = context$2$0['catch'](0);
case 7:
context$2$0.next = 9;
return _regeneratorRuntime.awrap(_get(Object.getPrototypeOf(SimulatorXcode8.prototype), 'cleanSafari', this).call(this, keepPrefs));
case 9:
case 'end':
return context$2$0.stop();
}
}, null, this, [[0, 5]]);
}
/**
* Clean/scrub the particular application on Simulator.
* @override
*
* @param {string} appFile - Application name minus ".app".
* @param {string} appBundleId - Bundle identifier of the application.
* @param {boolean} scrub - If `scrub` is false, we want to clean by deleting the app and all
* files associated with it. If `scrub` is true, we just want to delete the preferences and
* changed files.
* @return {array} Array of deletion promises.
*/
}, {
key: 'cleanCustomApp',
value: function cleanCustomApp(appFile, appBundleId) {
var scrub = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
return _regeneratorRuntime.async(function cleanCustomApp$(context$2$0) {
while (1) switch (context$2$0.prev = context$2$0.next) {
case 0:
context$2$0.prev = 0;
context$2$0.next = 3;
return _regeneratorRuntime.awrap((0, _nodeSimctl.terminate)(this.udid, appBundleId));
case 3:
context$2$0.next = 7;
break;
case 5:
context$2$0.prev = 5;
context$2$0.t0 = context$2$0['catch'](0);
case 7:
context$2$0.next = 9;
return _regeneratorRuntime.awrap(_get(Object.getPrototypeOf(SimulatorXcode8.prototype), 'cleanCustomApp', this).call(this, appFile, appBundleId, scrub));
case 9:
case 'end':
return context$2$0.stop();
}
}, null, this, [[0, 5]]);
}
}, {
key: 'uiClientBundleId',
get: function get() {
return UI_CLIENT_BUNDLE_ID;
}
}, {
key: 'startupPollBundleId',
get: function get() {
return SPRINGBOARD_BUNDLE_ID;
}
/**
* @return {number} The max number of milliseconds to wait until Simulator booting is completed.
*/
}, {
key: 'startupTimeout',
get: function get() {
return STARTUP_TIMEOUT;
}
}]);
return SimulatorXcode8;
})(_simulatorXcode72['default']);
exports['default'] = SimulatorXcode8;
module.exports = exports['default'];
// get_app_container subcommand fails for system applications,
// so we try the hidden appinfo subcommand, which prints correct info for
// system/hidden apps
// ignore error
// ignore error
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImxpYi9zaW11bGF0b3IteGNvZGUtOC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7K0JBQXFDLHFCQUFxQjs7K0JBQzlCLHFCQUFxQjs7OztzQkFDakMsVUFBVTs7Ozt3QkFDTyxVQUFVOzs0QkFDdEIsY0FBYzs7MEJBQzJDLGFBQWE7OztBQUkzRixJQUFNLGVBQWUsR0FBRyxHQUFHLEdBQUcsSUFBSSxDQUFDO0FBQ25DLElBQU0sc0JBQXNCLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUN6QyxJQUFNLHlCQUF5QixHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7QUFDM0MsSUFBTSxxQkFBcUIsR0FBRyx1QkFBdUIsQ0FBQztBQUN0RCxJQUFNLHVCQUF1QixHQUFHLHdCQUF3QixDQUFDO0FBQ3pELElBQU0sbUJBQW1CLEdBQUcsMkJBQTJCLENBQUM7QUFDeEQsSUFBTSx5QkFBeUIsR0FBRyxTQUE1Qix5QkFBeUIsQ0FBSSxRQUFRO1NBQUssSUFBSSxNQUFNLENBQUksUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLGVBQVk7Q0FBQSxDQUFDOztJQUVqRyxlQUFlO1lBQWYsZUFBZTs7QUFDUCxXQURSLGVBQWUsQ0FDTixJQUFJLEVBQUUsWUFBWSxFQUFFOzBCQUQ3QixlQUFlOztBQUVqQiwrQkFGRSxlQUFlLDZDQUVYLElBQUksRUFBRSxZQUFZLEVBQUU7Ozs7O0FBSzFCLFFBQUksQ0FBQyxZQUFZLEdBQUcsQ0FDbEIsaUJBQWlCLEVBQ2pCLDhDQUE4QyxFQUM5QyxpREFBaUQsRUFDakQsb0JBQW9CLENBQ3JCLENBQUM7R0FDSDs7Ozs7O2VBYkcsZUFBZTs7Ozs7Ozs7O1dBNEJLO1VBQ2hCLElBQUksUUFDSCxNQUFNLEVBQ1AsS0FBSzs7Ozs7QUFGTCxnQkFBSSxHQUFHLENBQUMsSUFBSSx1RkFBcUYsSUFBSSxDQUFDLGdCQUFnQixPQUFJOzs2Q0FDekcsd0JBQUssV0FBVyxFQUFFLElBQUksQ0FB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QUFTLElBQUksa0JBQWtCLENBQUEsQ0FBRSxDQUFDLENBQUM7OztBQUV0RSxnQ0FBSSxLQUFLLHVDQUFvQyxHQUFHLGNBQVEsT0FBTyxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLENBQUMsY0FBVyxDQUFDOzs7Ozs7O0tBQ3ZHOzs7Ozs7Ozs7O1dBUWlCO1VBQUMsU0FBUyx5REFBRyxJQUFJOzs7Ozs7NkNBRXpCLDJCQUFVLElBQUksQ0FBQyxJQUFJLEVBQUUsdUJBQXVCLENBQUM7Ozs7Ozs7Ozs7Ozt3RUFqTW5ELGVBQWUsNkNBcU1PLFNBQVM7Ozs7Ozs7S0FDbEM7Ozs7Ozs7Ozs7Ozs7OztXQWFvQix3QkFBQyxPQUFPLEVBQUUsV0FBVztVQUFFLEtBQUsseURBQUcsS0FBSzs7Ozs7OzZDQUUvQywyQkFBVSxJQUFJLENBQUMsSUFBSSxFQUFFLFdBQVcsQ0FBQzs7Ozs7Ozs7Ozs7O3dFQXJOdkMsZUFBZSxnREF5TlUsT0FBTyxFQUFFLFdBQVcsRUFBRSxLQUFLOzs7Ozs7O0tBQ3ZEOzs7U0F4TW9CLGVBQUc7QUFDdEIsYUFBTyxtQkFBbUIsQ0FBQztLQUM1Qjs7O1NBa0Z1QixlQUFHO0FBQ3pCLGFBQU8scUJBQXFCLENBQUM7S0FDOUI7Ozs7Ozs7U0FLa0IsZUFBRztBQUNwQixhQUFPLGVBQWUsQ0FBQztLQUN4Qjs7O1NBL0dHLGVBQWU7OztxQkE4Tk4sZUFBZSIsImZpbGUiOiJsaWIvc2ltdWxhdG9yLXhjb2RlLTguanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBCT09UX0NPTVBMRVRFRF9FVkVOVCB9IGZyb20gJy4vc2ltdWxhdG9yLXhjb2RlLTYnO1xuaW1wb3J0IFNpbXVsYXRvclhjb2RlNyBmcm9tICcuL3NpbXVsYXRvci14Y29kZS03JztcbmltcG9ydCBsb2cgZnJvbSAnLi9sb2dnZXInO1xuaW1wb3J0IHsgd2FpdEZvckNvbmRpdGlvbiB9IGZyb20gJ2FzeW5jYm94JztcbmltcG9ydCB7IGV4ZWMgfSBmcm9tICd0ZWVuX3Byb2Nlc3MnO1xuaW1wb3J0IHsgZ2V0QXBwQ29udGFpbmVyLCBvcGVuVXJsIGFzIHNpbWN0bE9wZW5VcmwsIHRlcm1pbmF0ZSwgYXBwSW5mbyB9IGZyb20gJ25vZGUtc2ltY3RsJztcblxuXG4vLyB0aGVzZSBzaW1zIGFyZSBzbG9vb29vb29vd1xuY29uc3QgU1RBUlRVUF9USU1FT1VUID0gMTIwICogMTAwMDtcbmNvbnN0IFNBRkFSSV9TVEFSVFVQX1RJTUVPVVQgPSAyNSAqIDEwMDA7XG5jb25zdCBVSV9DTElFTlRfU1RBUlRVUF9USU1FT1VUID0gNSAqIDEwMDA7XG5jb25zdCBTUFJJTkdCT0FSRF9CVU5ETEVfSUQgPSAnY29tLmFwcGxlLnNwcmluZ2JvYXJkJztcbmNvbnN0IE1PQklMRV9TQUZBUklfQlVORExFX0lEID0gJ2NvbS5hcHBsZS5tb2JpbGVzYWZhcmknO1xuY29uc3QgVUlfQ0xJRU5UX0JVTkRMRV9JRCA9ICdjb20uYXBwbGUuaXBob25lc2ltdWxhdG9yJztcbmNvbnN0IFBST0NFU1NfTEFVTkNIX09LX1BBVFRFUk4gPSAoYnVuZGxlSWQpID0+IG5ldyBSZWdFeHAoYCR7YnVuZGxlSWQucmVwbGFjZSgnLicsICdcXFxcLicpfTpcXFxccytcXFxcZCtgKTtcblxuY2xhc3MgU2ltdWxhdG9yWGNvZGU4IGV4dGVuZHMgU2ltdWxhdG9yWGNvZGU3IHtcbiAgY29uc3RydWN0b3IgKHVkaWQsIHhjb2RlVmVyc2lvbikge1xuICAgIHN1cGVyKHVkaWQsIHhjb2RlVmVyc2lvbik7XG5cbiAgICAvLyBsaXN0IG9mIGZpbGVzIHRvIGNoZWNrIGZvciB3aGVuIHNlZWluZyBpZiBhIHNpbXVsYXRvciBpcyBcImZyZXNoXCJcbiAgICAvLyAobWVhbmluZyBpdCBoYXMgbmV2ZXIgYmVlbiBib290ZWQpLlxuICAgIC8vIElmIHRoZXNlIGZpbGVzIGFyZSBwcmVzZW50LCB3ZSBhc3N1bWUgaXQncyBiZWVuIHN1Y2Nlc3NmdWxseSBib290ZWRcbiAgICB0aGlzLmlzRnJlc2hGaWxlcyA9IFtcbiAgICAgICdMaWJyYXJ5L0Nvb2tpZXMnLFxuICAgICAgJ0xpYnJhcnkvUHJlZmVyZW5jZXMvLkdsb2JhbFByZWZlcmVuY2VzLnBsaXN0JyxcbiAgICAgICdMaWJyYXJ5L1ByZWZlcmVuY2VzL2NvbS5hcHBsZS5zcHJpbmdib2FyZC5wbGlzdCcsXG4gICAgICAndmFyL3J1bi9zeXNsb2cucGlkJ1xuICAgIF07XG4gIH1cblxuICAvKipcbiAgICogQHJldHVybiB7c3RyaW5nfSBCdW5kbGUgaWRlbnRpZmllciBvZiBTaW11bGF0b3IgVUkgY2xpZW50LlxuICAgKi9cbiAgZ2V0IHVpQ2xpZW50QnVuZGxlSWQgKCkge1xuICAgIHJldHVybiBVSV9DTElFTlRfQlVORExFX0lEO1xuICB9XG5cbiAgLyoqXG4gICAqIENoZWNrIHRoZSBzdGF0ZSBvZiBTaW11bGF0b3IgVUkgY2xpZW50LlxuICAgKiBAT3ZlcnJpZGVcbiAgICpcbiAgICogQHJldHVybiB7Ym9vbGVhbn0gVHJ1ZSBvZiBpZiBVSSBjbGllbnQgaXMgcnVubmluZyBvciBmYWxzZSBvdGhlcndpc2UuXG4gICAqL1xuICBhc3luYyBpc1VJQ2xpZW50UnVubmluZyAoKSB7XG4gICAgY29uc3QgYXJncyA9IFsnLWUnLCBgdGVsbCBhcHBsaWNhdGlvbiBcIlN5c3RlbSBFdmVudHNcIiB0byBjb3VudCBwcm9jZXNzZXMgd2hvc2UgYnVuZGxlIGlkZW50aWZpZXIgaXMgXCIke3RoaXMudWlDbGllbnRCdW5kbGVJZH1cImBdO1xuICAgIGNvbnN0IHtzdGRvdXR9ID0gYXdhaXQgZXhlYygnb3Nhc2NyaXB0JywgYXJncyk7XG4gICAgY29uc3QgY291bnQgPSBwYXJzZUludChzdGRvdXQsIDEwKTtcbiAgICBpZiAoaXNOYU4oY291bnQpKSB7XG4gICAgICBsb2cuZXJyb3JBbmRUaHJvdyhgQ2Fubm90IHBhcnNlIHRoZSBjb3VudCBvZiBydW5uaW5nIFNpbXVsYXRvciBVSSBjbGllbnQgaW5zdGFuY2VzIGZyb20gJ29zYXNjcmlwdCAke2FyZ3N9JyBvdXRwdXQ6ICR7c3Rkb3V0fWApO1xuICAgIH1cbiAgICBsb2cuZGVidWcoYFRoZSBjb3VudCBvZiBydW5uaW5nIFNpbXVsYXRvciBVSSBjbGllbnQgaW5zdGFuY2VzIGlzICR7Y291bnR9YCk7XG4gICAgcmV0dXJuIGNvdW50ID49IDE7XG4gIH1cblxuICAvKipcbiAgICogS2lsbCB0aGUgVUkgY2xpZW50IGlmIGl0IGlzIHJ1bm5pbmcuXG4gICAqXG4gICAqIEBwYXJhbSB7Ym9vbGVhbn0gZm9yY2UgLSBTZXQgaXQgdG8gdHJ1ZSB0byBzZW5kIFNJR0tJTEwgc2lnbmFsIHRvIFNpbXVsYXRvciBwcm9jZXNzLlxuICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgU0lHVEVSTSB3aWxsIGJlIHNlbnQgYnkgZGVmYXVsdC5cbiAgICogQHJldHVybiB7Ym9vbGVhbn0gVHJ1ZSBpZiB0aGUgVUkgY2xpZW50IHdhcyBzdWNjZXNzZnVsbHkga2lsbGVkIG9yIGZhbHNlXG4gICAqICAgICAgICAgICAgICAgICAgIGlmIGl0IGlzIG5vdCBydW5uaW5nLlxuICAgKi9cbiAgYXN5bmMga2lsbFVJQ2xpZW50IChmb3JjZSA9IGZhbHNlKSB7XG4gICAgY29uc3Qgb3Nhc2NyaXB0QXJncyA9IFsnLWUnLCBgdGVsbCBhcHBsaWNhdGlvbiBcIlN5c3RlbSBFdmVudHNcIiB0byB1bml4IGlkIG9mIHByb2Nlc3NlcyB3aG9zZSBidW5kbGUgaWRlbnRpZmllciBpcyBcIiR7dGhpcy51aUNsaWVudEJ1bmRsZUlkfVwiYF07XG4gICAgY29uc3Qge3N0ZG91dH0gPSBhd2FpdCBleGVjKCdvc2FzY3JpcHQnLCBvc2FzY3JpcHRBcmdzKTtcbiAgICBpZiAoIXN0ZG91dC50cmltKCkubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGNvbnN0IGtpbGxBcmdzID0gZm9yY2UgPyBbJy05Jywgc3Rkb3V0LnRyaW0oKV0gOiBbc3Rkb3V0LnRyaW0oKV07XG4gICAgYXdhaXQgZXhlYygna2lsbCcsIGtpbGxBcmdzKTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdGFydCB0aGUgU2ltdWxhdG9yIFVJIGNsaWVudCB3aXRoIHRoZSBnaXZlbiBhcmd1bWVudHNcbiAgICogYW5kIHdhaXQgdW50aWwgaXQgaXMgcnVubmluZy5cbiAgICogQG92ZXJyaWRlXG4gICAqL1xuICBhc3luYyBzdGFydFVJQ2xpZW50IChvcHRzID0ge30pIHtcbiAgICBhd2FpdCBzdXBlci5zdGFydFVJQ2xpZW50KG9wdHMpO1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCB3YWl0Rm9yQ29uZGl0aW9uKGFzeW5jICgpID0+IHtcbiAgICAgICAgcmV0dXJuIGF3YWl0IHRoaXMuaXNVSUNsaWVudFJ1bm5pbmcoKTtcbiAgICAgIH0sIHt3YWl0TXM6IFVJX0NMSUVOVF9TVEFSVFVQX1RJTUVPVVQsIGludGVydmFsTXM6IDMwMH0pO1xuICAgIH0gY2F0Y2ggKGlnbikge1xuICAgICAgbG9nLndhcm4oYFRoZSBTaW11bGF0b3IgVUkgY2xpZW50IGlzIG5vdCBydW5uaW5nIGFmdGVyICR7VUlfQ0xJRU5UX1NUQVJUVVBfVElNRU9VVH0gbXMgdGltZW91dGApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBWZXJpZnkgd2hldGhlciB0aGUgcGFydGljdWxhciBhcHBsaWNhdGlvbiBpcyBpbnN0YWxsZWQgb24gU2ltdWxhdG9yLlxuICAgKiBAb3ZlcnJpZGVcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmd9IGJ1bmRsZUlkIC0gVGhlIGJ1bmRsZSBpZCBvZiB0aGUgYXBwbGljYXRpb24gdG8gYmUgY2hlY2tlZC5cbiAgICogQHJldHVybiB7Ym9vbGVhbn0gVHJ1ZSBpZiB0aGUgZ2l2ZW4gYXBwbGljYXRpb24gaXMgaW5zdGFsbGVkLlxuICAgKi9cbiAgYXN5bmMgaXNBcHBJbnN0YWxsZWQgKGJ1bmRsZUlkKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGFwcENvbnRhaW5lciA9IGF3YWl0IGdldEFwcENvbnRhaW5lcih0aGlzLnVkaWQsIGJ1bmRsZUlkLCBmYWxzZSk7XG4gICAgICByZXR1cm4gYXBwQ29udGFpbmVyLmVuZHNXaXRoKCcuYXBwJyk7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAvLyBnZXRfYXBwX2NvbnRhaW5lciBzdWJjb21tYW5kIGZhaWxzIGZvciBzeXN0ZW0gYXBwbGljYXRpb25zLFxuICAgICAgLy8gc28gd2UgdHJ5IHRoZSBoaWRkZW4gYXBwaW5mbyBzdWJjb21tYW5kLCB3aGljaCBwcmludHMgY29ycmVjdCBpbmZvIGZvclxuICAgICAgLy8gc3lzdGVtL2hpZGRlbiBhcHBzXG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBpbmZvID0gYXdhaXQgYXBwSW5mbyh0aGlzLnVkaWQsIGJ1bmRsZUlkKTtcbiAgICAgICAgcmV0dXJuIGluZm8uaW5kZXhPZignQXBwbGljYXRpb25UeXBlJykgIT09IC0xO1xuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEByZXR1cm4ge3N0cmluZ30gQXBwbGljYXRpb24gYnVuZGxlIGlkLCB3aGljaCBzaWduYWxzIHRoYXQgU2ltdWxhdG9yIGJvb3RpbmcgaXNcbiAgICogY29tcGV0ZWQgaWYgaXQgaXMgcnVubmluZy5cbiAgICovXG4gIGdldCBzdGFydHVwUG9sbEJ1bmRsZUlkICgpIHtcbiAgICByZXR1cm4gU1BSSU5HQk9BUkRfQlVORExFX0lEO1xuICB9XG5cbiAgLyoqXG4gICAqIEByZXR1cm4ge251bWJlcn0gVGhlIG1heCBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIHdhaXQgdW50aWwgU2ltdWxhdG9yIGJvb3RpbmcgaXMgY29tcGxldGVkLlxuICAgKi9cbiAgZ2V0IHN0YXJ0dXBUaW1lb3V0ICgpIHtcbiAgICByZXR1cm4gU1RBUlRVUF9USU1FT1VUO1xuICB9XG5cbiAgLyoqXG4gICAqIFZlcmlmeSB3aGV0aGVyIHRoZSBTaW11bGF0b3IgYm9vdGluZyBpcyBjb21wbGV0ZWQgYW5kL29yIHdhaXQgZm9yIGl0XG4gICAqIHVudGlsIHRoZSB0aW1lb3V0IGV4cGlyZXMuXG4gICAqIEBvdmVycmlkZVxuICAgKlxuICAgKiBAcGFyYW0ge251bWJlcn0gc3RhcnR1cFRpbWVvdXQgLSB0aGUgbnVtYmVyIG9mIG1pbGxpc2Vjb25kcyB0byB3YWl0IHVudGlsIGJvb3RpbmcgaXMgY29tcGxldGVkLlxuICAgKiBAZW1pdHMgQk9PVF9DT01QTEVURURfRVZFTlQgaWYgdGhlIGN1cnJlbnQgU2ltdWxhdG9yIGlzIHJlYWR5IHRvIGFjY2VwdCBzaW1jdGwgY29tbWFuZHMsIGxpa2UgJ2luc3RhbGwnLlxuICAgKi9cbiAgYXN5bmMgd2FpdEZvckJvb3QgKHN0YXJ0dXBUaW1lb3V0KSB7XG4gICAgY29uc3Qgc3RhcnR1cFRpbWVzdGFtcCA9IHByb2Nlc3MuaHJ0aW1lKCk7XG4gICAgbGV0IGxhc3RFcnJvciA9IG51bGw7XG4gICAgdHJ5IHtcbiAgICAgIGxldCBpc09uQm9vdENvbXBsZXRlZEVtaXR0ZWQgPSBmYWxzZTtcbiAgICAgIGF3YWl0IHdhaXRGb3JDb25kaXRpb24oYXN5bmMgKCkgPT4ge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIC8vICdzcHJpbmdib2FyZCcgcHJvY2VzcyBzaG91bGQgYmUgdGhlIGxhc3Qgb25lIHRvIHN0YXJ0IGFmdGVyIGJvb3RcbiAgICAgICAgICAvLyAnc2ltY3RsIGxhdW5jaCcgd2lsbCBibG9jayB1bnRpbCB0aGlzIHByb2Nlc3MgaXMgcnVubmluZyBvciBmYWlsIGlmIGJvb3RpbmcgaXMgc3RpbGwgaW4gcHJvZ3Jlc3NcbiAgICAgICAgICBjb25zdCB7c3Rkb3V0fSA9IGF3YWl0IGV4ZWMoJ3hjcnVuJywgWydzaW1jdGwnLCAnbGF1bmNoJywgdGhpcy51ZGlkLCB0aGlzLnN0YXJ0dXBQb2xsQnVuZGxlSWRdKTtcbiAgICAgICAgICBpZiAoUFJPQ0VTU19MQVVOQ0hfT0tfUEFUVEVSTih0aGlzLnN0YXJ0dXBQb2xsQnVuZGxlSWQpLnRlc3Qoc3Rkb3V0KSkge1xuICAgICAgICAgICAgaWYgKCFpc09uQm9vdENvbXBsZXRlZEVtaXR0ZWQpIHtcbiAgICAgICAgICAgICAgaXNPbkJvb3RDb21wbGV0ZWRFbWl0dGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgdGhpcy5lbWl0KEJPT1RfQ09NUExFVEVEX0VWRU5UKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgICAgfVxuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICBsYXN0RXJyb3IgPSBlcnIuc3RkZXJyIHx8IGVyci5tZXNzYWdlO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH0sIHt3YWl0TXM6IHN0YXJ0dXBUaW1lb3V0LCBpbnRlcnZhbE1zOiAxNTAwfSk7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBsb2cuZXJyb3JBbmRUaHJvdyhgU2ltdWxhdG9yIGlzIG5vdCBib290ZWQgYWZ0ZXIgJHtwcm9jZXNzLmhydGltZShzdGFydHVwVGltZXN0YW1wKVswXX0gc2Vjb25kcyBgICtcbiAgICAgICAgICAgICAgICAgICAgICAgIGBiZWNhdXNlIG9mOiAke2xhc3RFcnJvciB8fCAnYW4gdW5rbm93biBlcnJvcid9YCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIE9wZW4gdGhlIGdpdmVuIFVSTCBpbiBtb2JpbGUgU2FmYXJpIGJyb3dzZXIuXG4gICAqIFRoZSBicm93c2VyIHdpbGwgYmUgc3RhcnRlZCBhdXRvbWF0aWNhbGx5IGlmIGl0IGlzIG5vdCBydW5uaW5nLlxuICAgKiBAb3ZlcnJpZGVcbiAgICpcbiAgICogQHBhcmFtIHtzdHJpbmd9IHVybCAtIFRoZSBVUkwgdG8gYmUgb3BlbmVkLlxuICAgKi9cbiAgYXN5bmMgb3BlblVybCAodXJsKSB7XG4gICAgaWYgKCFhd2FpdCB0aGlzLmlzUnVubmluZygpKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFRyaWVkIHRvIG9wZW4gJHt1cmx9LCBidXQgU2ltdWxhdG9yIGlzIG5vdCBpbiBCb290ZWQgc3RhdGVgKTtcbiAgICB9XG4gICAgY29uc3QgbGF1bmNoVGltZXN0YW1wID0gcHJvY2Vzcy5ocnRpbWUoKTtcbiAgICBsZXQgbGFzdEVycm9yID0gbnVsbDtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgd2FpdEZvckNvbmRpdGlvbihhc3luYyAoKSA9PiB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgLy8gVGhpcyBpcyB0byBtYWtlIHN1cmUgU2FmYXJpIGlzIGFscmVhZHkgcnVubmluZ1xuICAgICAgICAgIGNvbnN0IHtzdGRvdXR9ID0gYXdhaXQgZXhlYygneGNydW4nLCBbJ3NpbWN0bCcsICdsYXVuY2gnLCB0aGlzLnVkaWQsIE1PQklMRV9TQUZBUklfQlVORExFX0lEXSk7XG4gICAgICAgICAgaWYgKFBST0NFU1NfTEFVTkNIX09LX1BBVFRFUk4oTU9CSUxFX1NBRkFSSV9CVU5ETEVfSUQpLnRlc3Qoc3Rkb3V0KSkge1xuICAgICAgICAgICAgYXdhaXQgc2ltY3RsT3BlblVybCh0aGlzLnVkaWQsIHVybCk7XG4gICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgIGxvZy5lcnJvcihgRmFpbGVkIHRvIG9wZW4gJyR7dXJsfScgaW4gU2FmYXJpLiBSZXRyeWluZy4uLmApO1xuICAgICAgICAgIGxhc3RFcnJvciA9IGVyci5zdGRlcnIgfHwgZXJyLm1lc3NhZ2U7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfSwge3dhaXRNczogU0FGQVJJX1NUQVJUVVBfVElNRU9VVCwgaW50ZXJ2YWxNczogNTAwfSk7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBsb2cuZXJyb3JBbmRUaHJvdyhgU2FmYXJpIGNhbm5vdCBvcGVuICcke3VybH0nIGFmdGVyICR7cHJvY2Vzcy5ocnRpbWUobGF1bmNoVGltZXN0YW1wKVswXX0gc2Vjb25kcyBgICtcbiAgICAgICAgICAgICAgICAgICAgICAgIGBiZWNhdXNlIG9mOiAke2xhc3RFcnJvciB8fCAnYW4gdW5rbm93biBlcnJvcid9YCk7XG4gICAgfVxuICAgIGxvZy5kZWJ1ZyhgU2FmYXJpIGhhcyBzdWNjZXNzZnVsbHkgb3BlbmVkICcke3VybH0nIGluICR7cHJvY2Vzcy5ocnRpbWUobGF1bmNoVGltZXN0YW1wKVswXX0gc2Vjb25kc2ApO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFuIHVwIHRoZSBkaXJlY3RvcmllcyBmb3IgbW9iaWxlIFNhZmFyaS5cbiAgICogQG92ZXJyaWRlXG4gICAqXG4gICAqIEBwYXJhbSB7Ym9vbGVhbn0ga2VlcFByZWZzIC0gV2hldGhlciB0byBrZWVwIFNhZmFyaSBwcmVmZXJlbmNlcyBmcm9tIGJlaW5nIGRlbGV0ZWQuXG4gICAqL1xuICBhc3luYyBjbGVhblNhZmFyaSAoa2VlcFByZWZzID0gdHJ1ZSkge1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0ZXJtaW5hdGUodGhpcy51ZGlkLCBNT0JJTEVfU0FGQVJJX0JVTkRMRV9JRCk7XG4gICAgfSBjYXRjaCAoaWduKSB7XG4gICAgICAvLyBpZ25vcmUgZXJyb3JcbiAgICB9XG4gICAgYXdhaXQgc3VwZXIuY2xlYW5TYWZhcmkoa2VlcFByZWZzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDbGVhbi9zY3J1YiB0aGUgcGFydGljdWxhciBhcHBsaWNhdGlvbiBvbiBTaW11bGF0b3IuXG4gICAqIEBvdmVycmlkZVxuICAgKlxuICAgKiBAcGFyYW0ge3N0cmluZ30gYXBwRmlsZSAtIEFwcGxpY2F0aW9uIG5hbWUgbWludXMgXCIuYXBwXCIuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBhcHBCdW5kbGVJZCAtIEJ1bmRsZSBpZGVudGlmaWVyIG9mIHRoZSBhcHBsaWNhdGlvbi5cbiAgICogQHBhcmFtIHtib29sZWFufSBzY3J1YiAtIElmIGBzY3J1YmAgaXMgZmFsc2UsIHdlIHdhbnQgdG8gY2xlYW4gYnkgZGVsZXRpbmcgdGhlIGFwcCBhbmQgYWxsXG4gICAqICAgZmlsZXMgYXNzb2NpYXRlZCB3aXRoIGl0LiBJZiBgc2NydWJgIGlzIHRydWUsIHdlIGp1c3Qgd2FudCB0byBkZWxldGUgdGhlIHByZWZlcmVuY2VzIGFuZFxuICAgKiAgIGNoYW5nZWQgZmlsZXMuXG4gICAqIEByZXR1cm4ge2FycmF5fSBBcnJheSBvZiBkZWxldGlvbiBwcm9taXNlcy5cbiAgICovXG4gIGFzeW5jIGNsZWFuQ3VzdG9tQXBwIChhcHBGaWxlLCBhcHBCdW5kbGVJZCwgc2NydWIgPSBmYWxzZSkge1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCB0ZXJtaW5hdGUodGhpcy51ZGlkLCBhcHBCdW5kbGVJZCk7XG4gICAgfSBjYXRjaCAoaWduKSB7XG4gICAgICAvLyBpZ25vcmUgZXJyb3JcbiAgICB9XG4gICAgYXdhaXQgc3VwZXIuY2xlYW5DdXN0b21BcHAoYXBwRmlsZSwgYXBwQnVuZGxlSWQsIHNjcnViKTtcbiAgfVxuXG59XG5cbmV4cG9ydCBkZWZhdWx0IFNpbXVsYXRvclhjb2RlODtcbiJdLCJzb3VyY2VSb290IjoiLi4vLi4ifQ==