UNPKG

@expo/cli

Version:
310 lines (309 loc) 13.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { getConnectedDevicesAsync: ()=>getConnectedDevicesAsync, runOnDevice: ()=>runOnDevice }); function _debug() { const data = /*#__PURE__*/ _interopRequireDefault(require("debug")); _debug = function() { return data; }; return data; } function _fs() { const data = /*#__PURE__*/ _interopRequireDefault(require("fs")); _fs = function() { return data; }; return data; } function _path() { const data = /*#__PURE__*/ _interopRequireDefault(require("path")); _path = function() { return data; }; return data; } const _clientManager = require("./ClientManager"); const _lockdowndClient = require("./client/LockdowndClient"); const _usbmuxdClient = require("./client/UsbmuxdClient"); const _afcprotocol = require("./protocol/AFCProtocol"); const _log = require("../../../log"); const _xcodeDeveloperDiskImagePrerequisite = require("../../../start/doctor/apple/XcodeDeveloperDiskImagePrerequisite"); const _devicectl = /*#__PURE__*/ _interopRequireWildcard(require("../../../start/platforms/ios/devicectl")); const _array = require("../../../utils/array"); const _delay = require("../../../utils/delay"); const _errors = require("../../../utils/errors"); const _exit = require("../../../utils/exit"); const _profile = require("../../../utils/profile"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for(var key in obj){ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } const debug = (0, _debug().default)("expo:apple-device"); async function getConnectedDevicesUsingNativeToolsAsync() { return (await _devicectl.getConnectedAppleDevicesAsync())// Filter out unpaired and unavailable devices. // TODO: We could improve this logic in the future to attempt pairing if specified. .filter((device)=>device.connectionProperties.pairingState === "paired" && device.connectionProperties.tunnelState !== "unavailable").map((device)=>{ return { name: device.deviceProperties.name, model: device.hardwareProperties.productType, osVersion: device.deviceProperties.osVersionNumber, udid: device.hardwareProperties.udid, deviceType: "device", connectionType: device.connectionProperties.transportType === "localNetwork" ? "Network" : "USB", osType: coercePlatformToOsType(device.hardwareProperties.platform) }; }); } function coercePlatformToOsType(platform) { // The only two devices I have to test against... switch(platform){ case "iOS": return "iOS"; case "xrOS": return "xrOS"; default: debug("Unknown devicectl platform (needs to be added to Expo CLI):", platform); return platform; } } function coerceUsbmuxdPlatformToOsType(platform) { // The only connectable device I have to test against... switch(platform){ case "iPhone": case "iPhone OS": return "iOS"; default: debug("Unknown usbmuxd platform (needs to be added to Expo CLI):", platform); return platform; } } async function getConnectedDevicesAsync() { const devices = await Promise.all([ // Prioritize native tools since they can provide more accurate information. // NOTE: xcrun is substantially slower than custom tooling. +1.5s vs 9ms. (0, _profile.profile)(getConnectedDevicesUsingNativeToolsAsync)(), (0, _profile.profile)(getConnectedDevicesUsingCustomToolingAsync)(), ]); return (0, _array.uniqBy)(devices.flat(), (device)=>device.udid); } /** * This supports devices that are running OS versions older than iOS 17. * * @returns a list of connected Apple devices. */ async function getConnectedDevicesUsingCustomToolingAsync() { const client = new _usbmuxdClient.UsbmuxdClient(_usbmuxdClient.UsbmuxdClient.connectUsbmuxdSocket()); const devices = await client.getDevices(); client.socket.end(); return Promise.all(devices.map(async (device)=>{ const socket = await new _usbmuxdClient.UsbmuxdClient(_usbmuxdClient.UsbmuxdClient.connectUsbmuxdSocket()).connect(device, 62078); const deviceValues = await new _lockdowndClient.LockdowndClient(socket).getAllValues(); socket.end(); var _DeviceName, ref; // TODO(EvanBacon): Add support for osType (ipad, watchos, etc) return { // TODO(EvanBacon): Better name name: (ref = (_DeviceName = deviceValues.DeviceName) != null ? _DeviceName : deviceValues.ProductType) != null ? ref : "unknown iOS device", model: deviceValues.ProductType, osVersion: deviceValues.ProductVersion, deviceType: "device", connectionType: device.Properties.ConnectionType, udid: device.Properties.SerialNumber, osType: coerceUsbmuxdPlatformToOsType(deviceValues.DeviceClass) }; })); } async function runOnDevice({ udid , appPath , bundleId , waitForApp , deltaPath , onProgress }) { debug("Running on device:", { udid, appPath, bundleId, waitForApp, deltaPath }); const clientManager = await _clientManager.ClientManager.create(udid); try { await mountDeveloperDiskImage(clientManager); const packageName = _path().default.basename(appPath); const destPackagePath = _path().default.join("PublicStaging", packageName); await uploadApp(clientManager, { appBinaryPath: appPath, destinationPath: destPackagePath }); const installer = await clientManager.getInstallationProxyClient(); await installer.installApp(destPackagePath, bundleId, { // https://github.com/ios-control/ios-deploy/blob/0f2ffb1e564aa67a2dfca7cdf13de47ce489d835/src/ios-deploy/ios-deploy.m#L2491-L2508 ApplicationsType: "Any", CFBundleIdentifier: bundleId, CloseOnInvalidate: "1", InvalidateOnDetach: "1", IsUserInitiated: "1", // Disable checking for wifi devices, this is nominally faster. PreferWifi: "0", // Only info I could find on these: // https://github.com/wwxxyx/Quectel_BG96/blob/310876f90fc1093a59e45e381160eddcc31697d0/Apple_Homekit/homekit_certification_tools/ATS%206/ATS%206/ATS.app/Contents/Frameworks/CaptureKit.framework/Versions/A/Resources/MobileDevice/MobileInstallation.h#L112-L121 PackageType: "Developer", ShadowParentKey: deltaPath }, onProgress); const { // TODO(EvanBacon): This can be undefined when querying App Clips. [bundleId]: appInfo , } = await installer.lookupApp([ bundleId ]); if (appInfo) { // launch fails with EBusy or ENotFound if you try to launch immediately after install await (0, _delay.delayAsync)(200); const debugServerClient = await launchApp(clientManager, { bundleId, appInfo, detach: !waitForApp }); if (waitForApp && debugServerClient) { (0, _exit.installExitHooks)(async ()=>{ // causes continue() to return debugServerClient.halt(); // give continue() time to return response await (0, _delay.delayAsync)(64); }); debug(`Waiting for app to close...\n`); const result = await debugServerClient.continue(); // TODO: I have no idea what this packet means yet (successful close?) // if not a close (ie, most likely due to halt from onBeforeExit), then kill the app if (result !== "W00") { await debugServerClient.kill(); } } } else { _log.Log.warn(`App "${bundleId}" installed but couldn't be launched. Open on device manually.`); } } finally{ clientManager.end(); } } /** Mount the developer disk image for Xcode. */ async function mountDeveloperDiskImage(clientManager) { const imageMounter = await clientManager.getMobileImageMounterClient(); // Check if already mounted. If not, mount. if (!(await imageMounter.lookupImage()).ImageSignature) { // verify DeveloperDiskImage exists (TODO: how does this work on Windows/Linux?) // TODO: if windows/linux, download? const version = await (await clientManager.getLockdowndClient()).getValue("ProductVersion"); const developerDiskImagePath = await _xcodeDeveloperDiskImagePrerequisite.XcodeDeveloperDiskImagePrerequisite.instance.assertAsync({ version }); const developerDiskImageSig = _fs().default.readFileSync(`${developerDiskImagePath}.signature`); await imageMounter.uploadImage(developerDiskImagePath, developerDiskImageSig); await imageMounter.mountImage(developerDiskImagePath, developerDiskImageSig); } } async function uploadApp(clientManager, { appBinaryPath , destinationPath }) { const afcClient = await clientManager.getAFCClient(); try { await afcClient.getFileInfo("PublicStaging"); } catch (err) { if (err instanceof _afcprotocol.AFCError && err.status === _afcprotocol.AFC_STATUS.OBJECT_NOT_FOUND) { await afcClient.makeDirectory("PublicStaging"); } else { throw err; } } await afcClient.uploadDirectory(appBinaryPath, destinationPath); } async function launchAppWithUsbmux(clientManager, { appInfo , detach }) { let tries = 0; while(tries < 3){ const debugServerClient = await clientManager.getDebugserverClient(); await debugServerClient.setMaxPacketSize(1024); await debugServerClient.setWorkingDir(appInfo.Container); await debugServerClient.launchApp(appInfo.Path, appInfo.CFBundleExecutable); const result = await debugServerClient.checkLaunchSuccess(); if (result === "OK") { if (detach) { // https://github.com/libimobiledevice/libimobiledevice/blob/25059d4c7d75e03aab516af2929d7c6e6d4c17de/tools/idevicedebug.c#L455-L464 const res = await debugServerClient.sendCommand("D", []); debug("Disconnect from debug server request:", res); if (res !== "OK") { console.warn("Something went wrong while attempting to disconnect from iOS debug server, you may need to reopen the app manually."); } } return debugServerClient; } else if (result === "EBusy" || result === "ENotFound") { debug("Device busy or app not found, trying to launch again in .5s..."); tries++; debugServerClient.socket.end(); await (0, _delay.delayAsync)(500); } else { throw new _errors.CommandError(`There was an error launching app: ${result}`); } } throw new _errors.CommandError("Unable to launch app, number of tries exceeded"); } /** * iOS 17 introduces a new protocol called RemoteXPC. * This is not yet implemented, so we fallback to devicectl. * * @see https://github.com/doronz88/pymobiledevice3/blob/master/misc/RemoteXPC.md#process-remoted */ async function launchApp(clientManager, { bundleId , appInfo , detach }) { try { return await launchAppWithUsbmux(clientManager, { appInfo, detach }); } catch (error) { debug("Failed to launch app with Usbmuxd, falling back to xcrun...", error); // Get the device UDID and close the connection, to allow `xcrun devicectl` to connect const deviceId = clientManager.device.Properties.SerialNumber; clientManager.end(); // Fallback to devicectl for iOS 17 support return await (0, _devicectl.launchAppWithDeviceCtl)(deviceId, bundleId); } } //# sourceMappingURL=AppleDevice.js.map