"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.isPlatformSupported = isPlatformSupported;
exports._isSimulatorInstalledAsync = _isSimulatorInstalledAsync;
exports._openAndBootSimulatorAsync = _openAndBootSimulatorAsync;
exports._isSimulatorRunningAsync = _isSimulatorRunningAsync;
exports._dirForSimulatorDevice = _dirForSimulatorDevice;
exports._quitSimulatorAsync = _quitSimulatorAsync;
exports._isExpoAppInstalledOnCurrentBootedSimulatorAsync = _isExpoAppInstalledOnCurrentBootedSimulatorAsync;
exports._waitForExpoAppInstalledOnCurrentBootedSimulatorAsync = _waitForExpoAppInstalledOnCurrentBootedSimulatorAsync;
exports._expoVersionOnCurrentBootedSimulatorAsync = _expoVersionOnCurrentBootedSimulatorAsync;
exports._checkExpoUpToDateAsync = _checkExpoUpToDateAsync;
exports._downloadSimulatorAppAsync = _downloadSimulatorAppAsync;
exports._installExpoOnSimulatorAsync = _installExpoOnSimulatorAsync;
exports._uninstallExpoAppFromSimulatorAsync = _uninstallExpoAppFromSimulatorAsync;
exports._simulatorCacheDirectory = _simulatorCacheDirectory;
exports.upgradeExpoAsync = upgradeExpoAsync;
exports._openUrlInSimulatorAsync = _openUrlInSimulatorAsync;
exports.openUrlInSimulatorSafeAsync = openUrlInSimulatorSafeAsync;
exports.openProjectAsync = openProjectAsync;
exports.openWebProjectAsync = openWebProjectAsync;

function _os() {
  const data = _interopRequireDefault(require("os"));

  _os = function () {
    return data;
  };

  return data;
}

function _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

function ConfigUtils() {
  const data = _interopRequireWildcard(require("@expo/config"));

  ConfigUtils = function () {
    return data;
  };

  return data;
}

function _delayAsync() {
  const data = _interopRequireDefault(require("delay-async"));

  _delayAsync = function () {
    return data;
  };

  return data;
}

function _globPromise() {
  const data = _interopRequireDefault(require("glob-promise"));

  _globPromise = function () {
    return data;
  };

  return data;
}

function osascript() {
  const data = _interopRequireWildcard(require("@expo/osascript"));

  osascript = function () {
    return data;
  };

  return data;
}

function _semver() {
  const data = _interopRequireDefault(require("semver"));

  _semver = function () {
    return data;
  };

  return data;
}

function _spawnAsync() {
  const data = _interopRequireDefault(require("@expo/spawn-async"));

  _spawnAsync = function () {
    return data;
  };

  return data;
}

function _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));

  _fsExtra = function () {
    return data;
  };

  return data;
}

function Analytics() {
  const data = _interopRequireWildcard(require("./Analytics"));

  Analytics = function () {
    return data;
  };

  return data;
}

function _Api() {
  const data = _interopRequireDefault(require("./Api"));

  _Api = function () {
    return data;
  };

  return data;
}

function _Logger() {
  const data = _interopRequireDefault(require("./Logger"));

  _Logger = function () {
    return data;
  };

  return data;
}

function _NotificationCode() {
  const data = _interopRequireDefault(require("./NotificationCode"));

  _NotificationCode = function () {
    return data;
  };

  return data;
}

function _UserSettings() {
  const data = _interopRequireDefault(require("./UserSettings"));

  _UserSettings = function () {
    return data;
  };

  return data;
}

function Versions() {
  const data = _interopRequireWildcard(require("./Versions"));

  Versions = function () {
    return data;
  };

  return data;
}

function _XDLError() {
  const data = _interopRequireDefault(require("./XDLError"));

  _XDLError = function () {
    return data;
  };

  return data;
}

function UrlUtils() {
  const data = _interopRequireWildcard(require("./UrlUtils"));

  UrlUtils = function () {
    return data;
  };

  return data;
}

function _Webpack() {
  const data = require("./Webpack");

  _Webpack = function () {
    return data;
  };

  return data;
}

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

// @ts-ignore
let _lastUrl = null;
const SUGGESTED_XCODE_VERSION = `8.2.0`;
const XCODE_NOT_INSTALLED_ERROR = 'Simulator not installed. Please visit https://developer.apple.com/xcode/download/ to download Xcode and the iOS simulator. If you already have the latest version of Xcode installed, you may have to run the command `sudo xcode-select -s /Applications/Xcode.app`.';

function isPlatformSupported() {
  return process.platform === 'darwin';
}

function _isLicenseOutOfDate(text) {
  if (!text) {
    return false;
  }

  let lower = text.toLowerCase();
  return lower.includes('xcode') && lower.includes('license');
}

async function _xcrunAsync(args) {
  try {
    return await (0, _spawnAsync().default)('xcrun', args);
  } catch (e) {
    if (_isLicenseOutOfDate(e.stdout) || _isLicenseOutOfDate(e.stderr)) {
      throw new (_XDLError().default)('XCODE_LICENSE_NOT_ACCEPTED', 'Xcode license is not accepted. Please run `sudo xcodebuild -license`.');
    } else {
      _Logger().default.global.error(`Error running \`xcrun ${args.join(' ')}\`: ${e.stderr}`);

      throw e;
    }
  }
} // Simulator installed


async function _isSimulatorInstalledAsync() {
  let result;

  try {
    result = (await osascript().execAsync('id of app "Simulator"')).trim();
  } catch (e) {
    console.error("Can't determine id of Simulator app; the Simulator is most likely not installed on this machine", e);

    _Logger().default.global.error(XCODE_NOT_INSTALLED_ERROR);

    return false;
  }

  if (result !== 'com.apple.iphonesimulator' && result !== 'com.apple.CoreSimulator.SimulatorTrampoline') {
    console.warn("Simulator is installed but is identified as '" + result + "'; don't know what that is.");

    _Logger().default.global.error(XCODE_NOT_INSTALLED_ERROR);

    return false;
  } // check xcode version


  try {
    const {
      stdout
    } = await (0, _spawnAsync().default)('xcodebuild', ['-version']); // find something that looks like a dot separated version number

    let matches = stdout.match(/[\d]{1,2}\.[\d]{1,3}/);

    if (!matches) {
      // very unlikely
      console.error('No version number found from `xcodebuild -version`.');

      _Logger().default.global.error('Unable to check Xcode version. Command ran successfully but no version number was found.');

      return false;
    } // we're cheating to use the semver lib, but it expects a proper patch version which xcode doesn't have


    const version = matches[0] + '.0';

    if (!_semver().default.valid(version)) {
      console.error('Invalid version number found: ' + matches[0]);
      return false;
    }

    if (_semver().default.lt(version, SUGGESTED_XCODE_VERSION)) {
      console.warn(`Found Xcode ${version}, which is older than the recommended Xcode ${SUGGESTED_XCODE_VERSION}.`);
    }
  } catch (e) {
    // how would this happen? presumably if Simulator id is found then xcodebuild is installed
    console.error(`Unable to check Xcode version: ${e}`);

    _Logger().default.global.error(XCODE_NOT_INSTALLED_ERROR);

    return false;
  } // make sure we can run simctl


  try {
    await _xcrunAsync(['simctl', 'help']);
  } catch (e) {
    if (e.isXDLError) {
      _Logger().default.global.error(e.toString());
    } else {
      console.warn(`Unable to run simctl: ${e.toString()}`);

      _Logger().default.global.error('xcrun may not be configured correctly. Try running `sudo xcode-select --reset` and running this again.');
    }

    return false;
  }

  return true;
} // Simulator opened


async function _openAndBootSimulatorAsync() {
  if (!(await _isSimulatorRunningAsync())) {
    _Logger().default.global.info('Opening iOS simulator');

    await (0, _spawnAsync().default)('open', ['-a', 'Simulator']);
    await _waitForDeviceToBoot();
  } else {
    let bootedDevice = await _bootedSimulatorDeviceAsync();

    if (!bootedDevice) {
      await _bootDefaultSimulatorDeviceAsync();
    }
  }
}

async function _isSimulatorRunningAsync() {
  let zeroMeansNo = (await osascript().execAsync('tell app "System Events" to count processes whose name is "Simulator"')).trim();

  if (zeroMeansNo === '0') {
    return false;
  }

  return true;
}

async function _bootDefaultSimulatorDeviceAsync() {
  _Logger().default.global.info(`Booting device in iOS simulator...`);

  try {
    let deviceUDID = await _getDefaultSimulatorDeviceUDIDAsync();

    if (!deviceUDID) {
      deviceUDID = (await _getFirstAvailableDeviceAsync()).udid;
    }

    return await _xcrunAsync(['simctl', 'boot', deviceUDID]);
  } catch (e) {
    _Logger().default.global.error(`There was a problem booting a device in iOS Simulator. Quit Simulator, and try again.`);

    throw e;
  }
}

async function _getDefaultSimulatorDeviceUDIDAsync() {
  try {
    const {
      stdout: defaultDeviceUDID
    } = await (0, _spawnAsync().default)('defaults', ['read', 'com.apple.iphonesimulator', 'CurrentDeviceUDID']);
    return defaultDeviceUDID.trim();
  } catch (e) {
    return null;
  }
}

async function _getFirstAvailableDeviceAsync() {
  const simulatorDeviceInfo = (await _listSimulatorDevicesAsync()).devices;
  let iOSRuntimesNewestToOldest = Object.keys(simulatorDeviceInfo).filter(runtime => runtime.includes('iOS')).reverse();
  const devices = simulatorDeviceInfo[iOSRuntimesNewestToOldest[0]];

  for (let i = 0; i < devices.length; i++) {
    const device = devices[i];

    if (device.isAvailable && device.name.includes('iPhone')) {
      return device;
    }
  }

  throw new Error('No iPhone devices available in Simulator.');
}

async function _listSimulatorDevicesAsync() {
  const result = await _xcrunAsync(['simctl', 'list', 'devices', '--json']);
  const info = JSON.parse(result.stdout);
  return info;
}

async function _waitForDeviceToBoot() {
  let bootedDevice;
  const start = Date.now();

  do {
    await (0, _delayAsync().default)(100);
    bootedDevice = await _bootedSimulatorDeviceAsync();

    if (Date.now() - start > 10000) {
      _Logger().default.global.error(`iOS Simulator device failed to boot. Try opening Simulator first, then running your app.`);

      throw new Error('Timed out waiting for iOS Simulator device to boot.');
    }
  } while (!bootedDevice);
}

async function _bootedSimulatorDeviceAsync() {
  let simulatorDeviceInfo = await _listSimulatorDevicesAsync();

  for (let runtime in simulatorDeviceInfo.devices) {
    let devices = simulatorDeviceInfo.devices[runtime];

    for (let i = 0; i < devices.length; i++) {
      let device = devices[i];

      if (device.state === 'Booted') {
        return device;
      }
    }
  }

  return null;
}

function _dirForSimulatorDevice(udid) {
  return _path().default.resolve(_os().default.homedir(), 'Library/Developer/CoreSimulator/Devices', udid);
}

async function _quitSimulatorAsync() {
  return await osascript().execAsync('tell application "Simulator" to quit');
} // Expo installed


async function _isExpoAppInstalledOnCurrentBootedSimulatorAsync() {
  let device = await _bootedSimulatorDeviceAsync();

  if (!device) {
    return false;
  }

  let simDir = await _dirForSimulatorDevice(device.udid);
  let matches = await (0, _globPromise().default)('./data/Containers/Data/Application/**/Snapshots/host.exp.Exponent{,**}', {
    cwd: simDir
  });
  return matches.length > 0;
}

async function _waitForExpoAppInstalledOnCurrentBootedSimulatorAsync() {
  if (await _isExpoAppInstalledOnCurrentBootedSimulatorAsync()) {
    return true;
  } else {
    await (0, _delayAsync().default)(100);
    return await _waitForExpoAppInstalledOnCurrentBootedSimulatorAsync();
  }
}

async function _expoVersionOnCurrentBootedSimulatorAsync() {
  let device = await _bootedSimulatorDeviceAsync();

  if (!device) {
    return null;
  }

  let simDir = await _dirForSimulatorDevice(device.udid);
  let matches = await (0, _globPromise().default)('./data/Containers/Bundle/Application/*/Exponent-*.app', {
    cwd: simDir
  });

  if (matches.length === 0) {
    return null;
  }

  let regex = /Exponent-([0-9.]+)\.app/;
  let regexMatch = regex.exec(matches[0]);

  if (!regexMatch) {
    return null;
  }

  return regexMatch[1];
}

async function _checkExpoUpToDateAsync() {
  let versions = await Versions().versionsAsync();
  let installedVersion = await _expoVersionOnCurrentBootedSimulatorAsync();

  if (!installedVersion || _semver().default.lt(installedVersion, versions.iosVersion)) {
    _Logger().default.notifications.warn({
      code: _NotificationCode().default.OLD_IOS_APP_VERSION
    }, 'This version of the Expo app is out of date. Uninstall the app and run again to upgrade.');
  }
}

async function _downloadSimulatorAppAsync(url) {
  // If specific URL given just always download it and don't use cache
  if (url) {
    let dir = _path().default.join(_simulatorCacheDirectory(), `Exponent-tmp.app`);

    await _Api().default.downloadAsync(url, dir, {
      extract: true
    });
    return dir;
  }

  let versions = await Versions().versionsAsync();

  let dir = _path().default.join(_simulatorCacheDirectory(), `Exponent-${versions.iosVersion}.app`);

  if (await _fsExtra().default.pathExists(dir)) {
    let filesInDir = await _fsExtra().default.readdir(dir);

    if (filesInDir.length > 0) {
      return dir;
    } else {
      _fsExtra().default.removeSync(dir);
    }
  }

  _fsExtra().default.mkdirpSync(dir);

  try {
    await _Api().default.downloadAsync(versions.iosUrl, dir, {
      extract: true
    });
  } catch (e) {
    _fsExtra().default.removeSync(dir);

    throw e;
  }

  return dir;
} // url: Optional URL of Exponent.app tarball to download


async function _installExpoOnSimulatorAsync(url) {
  _Logger().default.global.info(`Downloading the latest version of Expo client app`);

  _Logger().default.notifications.info({
    code: _NotificationCode().default.START_LOADING
  });

  let dir = await _downloadSimulatorAppAsync(url);

  _Logger().default.notifications.info({
    code: _NotificationCode().default.STOP_LOADING
  });

  _Logger().default.global.info('Installing Expo client on iOS simulator');

  _Logger().default.notifications.info({
    code: _NotificationCode().default.START_LOADING
  });

  let result = await _xcrunAsync(['simctl', 'install', 'booted', dir]);

  _Logger().default.notifications.info({
    code: _NotificationCode().default.STOP_LOADING
  });

  return result;
}

async function _uninstallExpoAppFromSimulatorAsync() {
  try {
    _Logger().default.global.info('Uninstalling Expo client from iOS simulator.');

    await _xcrunAsync(['simctl', 'uninstall', 'booted', 'host.exp.Exponent']);
  } catch (e) {
    if (e.message && e.message.includes('No devices are booted.')) {
      return;
    } else {
      console.error(e);
      throw e;
    }
  }
}

function _simulatorCacheDirectory() {
  let dotExpoHomeDirectory = _UserSettings().default.dotExpoHomeDirectory();

  let dir = _path().default.join(dotExpoHomeDirectory, 'ios-simulator-app-cache');

  _fsExtra().default.mkdirpSync(dir);

  return dir;
}

async function upgradeExpoAsync() {
  if (!(await _isSimulatorInstalledAsync())) {
    return false;
  }

  await _openAndBootSimulatorAsync();
  await _uninstallExpoAppFromSimulatorAsync();
  let installResult = await _installExpoOnSimulatorAsync();

  if (installResult.status !== 0) {
    return false;
  }

  if (_lastUrl) {
    _Logger().default.global.info(`Opening ${_lastUrl} in Expo.`);

    await _xcrunAsync(['simctl', 'openurl', 'booted', _lastUrl]);
    _lastUrl = null;
  }

  return true;
} // Open Url


async function _openUrlInSimulatorAsync(url) {
  return await _xcrunAsync(['simctl', 'openurl', 'booted', url]);
}

async function openUrlInSimulatorSafeAsync(url, isDetached = false) {
  if (!(await _isSimulatorInstalledAsync())) {
    return {
      success: false,
      msg: 'Unable to verify Xcode and Simulator installation.'
    };
  }

  try {
    await _openAndBootSimulatorAsync();

    if (!isDetached && !(await _isExpoAppInstalledOnCurrentBootedSimulatorAsync())) {
      await _installExpoOnSimulatorAsync();
      await _waitForExpoAppInstalledOnCurrentBootedSimulatorAsync();
    }

    if (!isDetached) {
      _lastUrl = url;

      _checkExpoUpToDateAsync(); // let this run in background

    }

    _Logger().default.global.info(`Opening ${url} in iOS simulator`);

    await _openUrlInSimulatorAsync(url);
  } catch (e) {
    if (e.isXDLError) {
      // Hit some internal error, don't try again.
      // This includes Xcode license errors
      _Logger().default.global.error(e.message);

      return {
        success: false,
        msg: `${e.toString()}`
      };
    }

    if (isDetached) {
      _Logger().default.global.error(`Error running app. Have you installed the app already using Xcode? Since you are detached you must build manually. ${e.toString()}`);
    } else {
      _Logger().default.global.error(`Error installing or running app. ${e.toString()}`);
    }

    return {
      success: false,
      msg: `${e.toString()}`
    };
  }

  Analytics().logEvent('Open Url on Device', {
    platform: 'ios'
  });
  return {
    success: true
  };
}

async function openProjectAsync(projectRoot) {
  let projectUrl = await UrlUtils().constructManifestUrlAsync(projectRoot, {
    hostType: 'localhost'
  });
  let {
    exp
  } = await ConfigUtils().readConfigJsonAsync(projectRoot);
  let result = await openUrlInSimulatorSafeAsync(projectUrl, !!exp.isDetached);

  if (result.success) {
    return {
      success: true,
      url: projectUrl
    };
  } else {
    return {
      success: result.success,
      error: result.msg
    };
  }
}

async function openWebProjectAsync(projectRoot) {
  const projectUrl = await (0, _Webpack().getUrlAsync)(projectRoot);

  if (projectUrl === null) {
    return {
      success: false,
      error: `The web project has not been started yet`
    };
  }

  const result = await openUrlInSimulatorSafeAsync(projectUrl, true);

  if (result.success) {
    return {
      success: true,
      url: projectUrl
    };
  } else {
    return {
      success: result.success,
      error: result.msg
    };
  }
}
//# sourceMappingURL=__sourcemaps__/Simulator.js.map