UNPKG

mobile-cli-lib

Version:
343 lines (342 loc) 16.8 kB
"use strict"; var net = require("net"); var ref = require("ref"); var path = require("path"); var util = require("util"); var ios_core_1 = require("./ios-core"); var iOSProxyServices = require("./ios-proxy-services"); var applicationManagerPath = require("./ios-application-manager"); var fileSystemPath = require("./ios-device-file-system"); var constants = require("../../../constants"); var IOSDevice = (function () { function IOSDevice(devicePointer, $coreFoundation, $errors, $fs, $injector, $logger, $mobileDevice, $devicePlatformsConstants, $hostInfo, $options, $iOSDeviceProductNameMapper, $processService, $xcodeSelectService) { this.devicePointer = devicePointer; this.$coreFoundation = $coreFoundation; this.$errors = $errors; this.$fs = $fs; this.$injector = $injector; this.$logger = $logger; this.$mobileDevice = $mobileDevice; this.$devicePlatformsConstants = $devicePlatformsConstants; this.$hostInfo = $hostInfo; this.$options = $options; this.$iOSDeviceProductNameMapper = $iOSDeviceProductNameMapper; this.$processService = $processService; this.$xcodeSelectService = $xcodeSelectService; this.mountImageCallbackPtr = null; this.mountImageCallbackPtr = ios_core_1.CoreTypes.am_device_mount_image_callback.toPointer(IOSDevice.mountImageCallback); this.applicationManager = this.$injector.resolve(applicationManagerPath.IOSApplicationManager, { device: this, devicePointer: this.devicePointer }); this.fileSystem = this.$injector.resolve(fileSystemPath.IOSDeviceFileSystem, { device: this, devicePointer: this.devicePointer }); this.deviceInfo = { identifier: this.$coreFoundation.convertCFStringToCString(this.$mobileDevice.deviceCopyDeviceIdentifier(this.devicePointer)), vendor: "Apple", platform: this.$devicePlatformsConstants.iOS, status: constants.CONNECTED_STATUS, errorHelp: null, type: "Device" }; var productType = this.getValue("ProductType"); var deviceName = this.getValue("DeviceName") || productType; var displayName = this.$iOSDeviceProductNameMapper.resolveProductName(deviceName) || deviceName; this.deviceInfo.displayName = displayName; this.deviceInfo.model = this.$iOSDeviceProductNameMapper.resolveProductName(productType); this.deviceInfo.version = this.getValue("ProductVersion"); this.deviceInfo.color = this.getValue("DeviceColor"); this.deviceInfo.isTablet = productType && productType.toLowerCase().indexOf("ipad") !== -1; this.deviceInfo.activeArchitecture = this.getActiveArchitecture(productType); if (this.deviceInfo.errorHelp) { this.deviceInfo.status = constants.UNREACHABLE_STATUS; } } IOSDevice.prototype.getActiveArchitecture = function (productType) { var activeArchitecture = ""; if (productType) { productType = productType.toLowerCase().trim(); var majorVersionAsString = productType.match(/.*?(\d+)\,(\d+)/)[1]; var majorVersion = parseInt(majorVersionAsString); var isArm64Architecture = false; if (_.startsWith(productType, "iphone")) { isArm64Architecture = majorVersion >= 6; } else if (_.startsWith(productType, "ipad")) { isArm64Architecture = majorVersion >= 4; } else if (_.startsWith(productType, "ipod")) { isArm64Architecture = majorVersion >= 7; } activeArchitecture = isArm64Architecture ? "arm64" : "armv7"; } return activeArchitecture; }; Object.defineProperty(IOSDevice.prototype, "isEmulator", { get: function () { return false; }, enumerable: true, configurable: true }); IOSDevice.prototype.getApplicationInfo = function (applicationIdentifier) { return this.applicationManager.getApplicationInfo(applicationIdentifier); }; IOSDevice.mountImageCallback = function (dictionary, user) { var coreFoundation = $injector.resolve("coreFoundation"); var logger = $injector.resolve("logger"); var jsDictionary = coreFoundation.cfTypeTo(dictionary); logger.info("[Mounting] %s", jsDictionary["Status"]); }; IOSDevice.prototype.getValue = function (value) { try { this.connect(); this.startSession(); var cfValue = this.$coreFoundation.createCFString(value); return this.$coreFoundation.convertCFStringToCString(this.$mobileDevice.deviceCopyValue(this.devicePointer, null, cfValue)); } catch (err) { this.deviceInfo.errorHelp = this.deviceInfo.errorHelp || err.message.replace(/ Result code is: \d+$/, ""); this.$logger.trace("Error while trying to get " + value + " for iOS device. Error is: ", err); } finally { this.stopSession(); this.disconnect(); } return null; }; IOSDevice.prototype.validateResult = function (result, error) { if (result !== 0) { this.$errors.fail({ formatStr: error + ". Result code is: " + result, errorCode: result }); } else { this.deviceInfo.status = constants.CONNECTED_STATUS; } }; IOSDevice.prototype.isPaired = function () { return this.$mobileDevice.deviceIsPaired(this.devicePointer) !== 0; }; IOSDevice.prototype.pair = function () { var result = this.$mobileDevice.devicePair(this.devicePointer); this.validateResult(result, "Make sure you have trusted the computer from your device. If your phone is locked with a passcode, unlock then reconnect it"); return result; }; IOSDevice.prototype.validatePairing = function () { var result = this.$mobileDevice.deviceValidatePairing(this.devicePointer); this.validateResult(result, "Unable to validate pairing"); return result; }; IOSDevice.prototype.connect = function () { var result = this.$mobileDevice.deviceConnect(this.devicePointer); this.validateResult(result, "Unable to connect to device"); if (!this.isPaired()) { this.pair(); } return this.validatePairing(); }; IOSDevice.prototype.disconnect = function () { var result = this.$mobileDevice.deviceDisconnect(this.devicePointer); if (result > 0) { this.$logger.warn("Unable to disconnect. Result is: " + result); } }; IOSDevice.prototype.startSession = function () { var result = this.$mobileDevice.deviceStartSession(this.devicePointer); this.validateResult(result, "Unable to start session"); }; IOSDevice.prototype.stopSession = function () { var result = this.$mobileDevice.deviceStopSession(this.devicePointer); if (result > 0) { this.$logger.warn("Unable to stop session. Result is: " + result); } }; IOSDevice.prototype.getDeviceValue = function (value) { var deviceCopyValue = this.$mobileDevice.deviceCopyValue(this.devicePointer, null, this.$coreFoundation.createCFString(value)); return this.$coreFoundation.convertCFStringToCString(deviceCopyValue); }; IOSDevice.prototype.tryExecuteFunction = function (func) { this.connect(); try { this.startSession(); try { return func.apply(this, []); } finally { this.stopSession(); } } finally { this.disconnect(); } }; IOSDevice.prototype.findDeveloperDiskImageDirectoryPath = function () { var _this = this; return (function () { var developerDirectory = _this.$xcodeSelectService.getDeveloperDirectoryPath().wait(); var buildVersion = _this.getDeviceValue("BuildVersion"); var productVersion = _this.getDeviceValue("ProductVersion"); var productVersionParts = productVersion.split("."); var productMajorVersion = productVersionParts[0]; var productMinorVersion = productVersionParts[1]; var developerDiskImagePath = path.join(developerDirectory, "Platforms", "iPhoneOS.platform", "DeviceSupport"); var supportPaths = _this.$fs.readDirectory(developerDiskImagePath).wait(); var supportPath = null; _.each(supportPaths, function (sp) { var parts = sp.split(' '); var version = parts[0]; var versionParts = version.split("."); var supportPathData = { version: version, majorVersion: versionParts[0], minorVersion: versionParts[1], build: parts.length > 1 ? parts[1].replace(/[()]/, function () { return ""; }) : null, path: path.join(developerDiskImagePath, sp) }; if (supportPathData.majorVersion === productMajorVersion) { if (!supportPath) { supportPath = supportPathData; } else { if (supportPathData.minorVersion === productMinorVersion) { if (supportPathData.build === buildVersion) { supportPath = supportPathData; } else { if (supportPath.build !== supportPathData.build || supportPath.build === null) { supportPath = supportPathData; } } } } } }); if (!supportPath) { _this.$errors.fail("Unable to find device support path. Verify that you have installed sdk compatible with your device version."); } return supportPath.path; }).future()(); }; IOSDevice.prototype.mountImage = function () { var _this = this; return (function () { var imagePath = _this.$options.ddi; if (_this.$hostInfo.isWindows) { if (!imagePath) { _this.$errors.fail("On windows operating system you must specify the path to developer disk image using --ddi option"); } var imageSignature = _this.$fs.readFile(util.format("%s.signature", imagePath)).wait(); var imageSize = _this.$fs.getFsStats(imagePath).wait().size; var imageMounterService = _this.startService(iOSProxyServices.MobileServices.MOBILE_IMAGE_MOUNTER); var plistService = _this.$injector.resolve(ios_core_1.PlistService, { service: imageMounterService, format: ios_core_1.CoreTypes.kCFPropertyListXMLFormat_v1_0 }); var result = plistService.exchange({ Command: "ReceiveBytes", ImageSize: imageSize, ImageType: "Developer", ImageSignature: imageSignature }).wait(); if (result.Status === "ReceiveBytesAck") { var fileData = _this.$fs.readFile(imagePath).wait(); plistService.sendAll(fileData); } else { var afcService = _this.startService(iOSProxyServices.MobileServices.APPLE_FILE_CONNECTION); var afcClient = _this.$injector.resolve(iOSProxyServices.AfcClient, { service: afcService }); afcClient.transfer(imagePath, "PublicStaging/staging.dimage").wait(); } try { result = plistService.exchange({ Command: "MountImage", ImageType: "Developer", ImageSignature: imageSignature, ImagePath: "/let/mobile/Media/PublicStaging/staging.dimage" }).wait(); if (result.Error) { _this.$errors.fail("Unable to mount image. %s", result.Error); } if (result.Status) { _this.$logger.info("Mount image: %s", result.Status); } } finally { plistService.close(); } } else { var func = function () { var developerDiskImageDirectoryPath = _this.findDeveloperDiskImageDirectoryPath().wait(); imagePath = path.join(developerDiskImageDirectoryPath, "DeveloperDiskImage.dmg"); _this.$logger.info("Mounting %s", imagePath); var signature = _this.$fs.readFile(util.format("%s.signature", imagePath)).wait(); var cfImagePath = _this.$coreFoundation.createCFString(imagePath); var cfOptions = _this.$coreFoundation.cfTypeFrom({ ImageType: "Developer", ImageSignature: signature }); var result = _this.$mobileDevice.deviceMountImage(_this.devicePointer, cfImagePath, cfOptions, _this.mountImageCallbackPtr); if (result !== 0 && result !== IOSDevice.IMAGE_ALREADY_MOUNTED_ERROR_CODE) { if (result === IOSDevice.INCOMPATIBLE_IMAGE_SIGNATURE_ERROR_CODE) { _this.$logger.warn("Unable to mount image %s on device %s.", imagePath, _this.deviceInfo.identifier); } else { _this.$errors.fail("Unable to mount image on device."); } } }; _this.tryExecuteFunction(func); } }).future()(); }; IOSDevice.prototype.getInterfaceType = function () { return this.$mobileDevice.deviceGetInterfaceType(this.devicePointer); }; IOSDevice.prototype.startService = function (serviceName) { var _this = this; var func = function () { var socket = ref.alloc("int"); var result = _this.$mobileDevice.deviceStartService(_this.devicePointer, _this.$coreFoundation.createCFString(serviceName), socket); _this.validateResult(result, "Unable to start service " + serviceName); return ref.deref(socket); }; return this.tryExecuteFunction(func); }; IOSDevice.prototype.openDeviceLogStream = function () { if (this.deviceInfo.status !== constants.UNREACHABLE_STATUS) { var iOSSystemLog = this.$injector.resolve(iOSProxyServices.IOSSyslog, { device: this }); iOSSystemLog.read(); } }; IOSDevice.prototype.connectToPort = function (port) { var interfaceType = this.getInterfaceType(); if (interfaceType === IOSDevice.INTERFACE_USB) { var connectionId = this.$mobileDevice.deviceGetConnectionId(this.devicePointer); var socketRef = ref.alloc(ios_core_1.CoreTypes.intType); this.$mobileDevice.uSBMuxConnectByPort(connectionId, this.htons(port), socketRef); var socketValue = socketRef.deref(); var socket_1; if (socketValue < 0) { socket_1 = new net.Socket(); process.nextTick(function () { return socket_1.emit("error", new Error("USBMuxConnectByPort returned bad file descriptor")); }); } else { socket_1 = new net.Socket({ fd: socketValue }); process.nextTick(function () { return socket_1.emit("connect"); }); } this._socket = socket_1; this.$processService.attachToProcessExitSignals(this, this.destroySocket); return socket_1; } return null; }; IOSDevice.prototype.destroySocket = function () { if (this._socket) { this._socket.destroy(); this._socket = null; } }; IOSDevice.prototype.htons = function (port) { var result = (port & 0xff00) >> 8 | (port & 0x00ff) << 8; return result; }; IOSDevice.IMAGE_ALREADY_MOUNTED_ERROR_CODE = 3892314230; IOSDevice.INCOMPATIBLE_IMAGE_SIGNATURE_ERROR_CODE = 3892314163; IOSDevice.INTERFACE_USB = 1; return IOSDevice; }()); exports.IOSDevice = IOSDevice; $injector.register("iOSDevice", IOSDevice);