appium-ios-device
Version:
Appium API for dealing with iOS devices
281 lines • 11.2 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getConnectedDevices = getConnectedDevices;
exports.getOSVersion = getOSVersion;
exports.getDeviceName = getDeviceName;
exports.getDeviceTime = getDeviceTime;
exports.startLockdownSession = startLockdownSession;
exports.connectPort = connectPort;
exports.connectPortSSL = connectPortSSL;
exports.getDeviceInfo = getDeviceInfo;
exports.fetchImageFromGithubRepo = fetchImageFromGithubRepo;
exports.getMaxFrameLength = getMaxFrameLength;
const usbmux_1 = require("./usbmux");
const ssl_helper_1 = require("./ssl-helper");
const lodash_1 = __importDefault(require("lodash"));
const logger_1 = require("./logger");
const list_developer_image_1 = require("./imagemounter/utils/list_developer_image");
// https://github.com/samdmarshall/iOS-Internals/blob/master/lockbot/lockbot/lockdown_keys.h
const LOCKDOWN_REQUEST = {
DEVICE_TIME: { Key: 'TimeIntervalSince1970' },
DEVICE_UTC_OFFSET: { Key: 'TimeZoneOffsetFromUTC' },
DEVICE_TIME_ZONE: { Key: 'TimeZone' },
DEVICE_VERSION: { Key: 'ProductVersion' },
DEVICE_NAME: { Key: 'DeviceName' }
};
/**
* Retrieves the udids of the connected devices
*
* @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed
* @returns {Promise<string[]>} The list of device serial numbers (udid) or
* an empty list if no devices are connected
*/
async function getConnectedDevices(socket = null) {
let usbmux;
try {
usbmux = new usbmux_1.Usbmux(socket || await (0, usbmux_1.getDefaultSocket)());
}
catch (e) {
logger_1.log.debug(e);
return [];
}
try {
const devices = await usbmux.listDevices();
const udids = devices.map((device) => device.Properties.SerialNumber);
return lodash_1.default.uniq(udids);
}
finally {
usbmux.close();
}
}
/**
* Retrieves the os version of the device
*
* @param {string} udid Device UDID
* @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed
* @returns {Promise<string>}
*/
async function getOSVersion(udid, socket = null) {
const usbmux = new usbmux_1.Usbmux(socket || await (0, usbmux_1.getDefaultSocket)());
try {
// lockdown doesn't need to be closed since it uses the same socket usbmux uses
const lockdown = await usbmux.connectLockdown(udid);
return await lockdown.getValue(LOCKDOWN_REQUEST.DEVICE_VERSION);
}
finally {
usbmux.close();
}
}
/**
* Retrieves the name of the device
*
* @param {string} udid Device UDID
* @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed
* @returns {Promise<string>}
*/
async function getDeviceName(udid, socket = null) {
const usbmux = new usbmux_1.Usbmux(socket || await (0, usbmux_1.getDefaultSocket)());
try {
// lockdown doesn't need to be closed since it uses the same socket usbmux uses
const lockdown = await usbmux.connectLockdown(udid);
return await lockdown.getValue(LOCKDOWN_REQUEST.DEVICE_NAME);
}
finally {
usbmux.close();
}
}
/**
* Returns all available device values
* @param {string} udid Device UDID
* @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed
* @returns {Promise<any>} Returns available default device values via lockdown.
* e.g.
* {
* "BasebandCertId"=>3840149528,
* "BasebandKeyHashInformation"=>
* {"AKeyStatus"=>2,
* "SKeyHash"=>{
* "type"=>"Buffer",
* "data"=>[187, 239, ....]},
* "SKeyStatus"=>0},
* "BasebandSerialNumber"=>{"type"=>"Buffer", "data"=>[...]},
* "BasebandVersion"=>"11.01.02",
* "BoardId"=>2,
* "BuildVersion"=>"19C56",
* "CPUArchitecture"=>"arm64",
* "ChipID"=>32768,
* "DeviceClass"=>"iPhone",
* "DeviceColor"=>"#c8caca",
* "DeviceName"=>"kazu",
* "DieID"=>1111111111111,
* "HardwareModel"=>"N69uAP",
* "HasSiDP"=>true,
* "PartitionType"=>"GUID_partition_scheme",
* "ProductName"=>"iPhone OS",
* "ProductType"=>"iPhone8,4",
* "ProductVersion"=>"15.2",
* "ProductionSOC"=>true,
* "ProtocolVersion"=>"2",
* "SupportedDeviceFamilies"=>[1],
* "TelephonyCapability"=>true,
* "UniqueChipID"=>1111111111111,
* "UniqueDeviceID"=>"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
* "WiFiAddress"=>"00:00:00:00:00:00"
* }
*/
async function getDeviceInfo(udid, socket = null) {
const usbmux = new usbmux_1.Usbmux(socket || await (0, usbmux_1.getDefaultSocket)());
try {
const lockdown = await usbmux.connectLockdown(udid);
return await lockdown.getValue();
}
finally {
usbmux.close();
}
}
/**
* @typedef {Object} DeviceTime
*
* @property {number} timestamp Unix timestamp in seconds since 1970-01-01T00:00:00Z
* @property {number} utcOffset The difference in minutes between the UTC time and the local device time.
* Can be negative.
* @property {string} timeZone Time zone name configured on the device, for example `Europe/Paris`
*/
/**
* Retrieves the local time from the device under test
*
* @param {string} udid Device UDID
* @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed
* @returns {Promise<DeviceTime>}
*/
async function getDeviceTime(udid, socket = null) {
const lockdown = await startLockdownSession(udid, socket);
try {
return {
timestamp: await lockdown.getValue(LOCKDOWN_REQUEST.DEVICE_TIME),
// Apple returns utcOffset in seconds which doesnt comply with the general standard
utcOffset: await lockdown.getValue(LOCKDOWN_REQUEST.DEVICE_UTC_OFFSET) / 60,
timeZone: await lockdown.getValue(LOCKDOWN_REQUEST.DEVICE_TIME_ZONE),
};
}
finally {
lockdown.close();
}
}
/**
* Starts a lockdown session on the given device
*
* @param {string} udid Device UDID
* @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed
* @returns {Promise<import('./lockdown').Lockdown>}
*/
async function startLockdownSession(udid, socket = null) {
const usbmux = new usbmux_1.Usbmux(socket || await (0, usbmux_1.getDefaultSocket)());
try {
const pairRecord = await usbmux.readPairRecord(udid);
if (!pairRecord) {
throw new Error(`Could not find a pair record for device '${udid}'. Please first pair with the device`);
}
// lockdown doesn't need to be closed since it uses the same socket usbmux uses
const lockdown = await usbmux.connectLockdown(udid);
await lockdown.startSession(pairRecord.HostID, pairRecord.SystemBUID);
lockdown.enableSessionSSL(pairRecord.HostPrivateKey, pairRecord.HostCertificate);
return lockdown;
}
catch (e) {
usbmux.close();
throw e;
}
}
/**
* Connects to a given port with the certs and keys used in the pairing process
*
* @param {string} udid Device UDID
* @param {number} port Port to connect
* @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed
* @param {boolean} handshakeOnly only handshake and return the initial socket
* @returns {Promise<import('tls').TLSSocket|Object>} The socket or the object returned in the callback if the callback function exists
*/
async function connectPortSSL(udid, port, socket = null, handshakeOnly = false) {
const usbmux = new usbmux_1.Usbmux(socket || await (0, usbmux_1.getDefaultSocket)());
try {
const device = await usbmux.findDevice(udid);
if (!device) {
throw new Error(`Could not find the expected device '${udid}'`);
}
const pairRecord = await usbmux.readPairRecord(udid);
if (!pairRecord) {
throw new Error(`Could not find a pair record for device '${udid}'. Please first pair with the device`);
}
const socket = await usbmux.connect(device.Properties.DeviceID, port, undefined);
return handshakeOnly ?
await (0, ssl_helper_1.enableSSLHandshakeOnly)(socket, pairRecord.HostPrivateKey, pairRecord.HostCertificate) :
(0, ssl_helper_1.upgradeToSSL)(socket, pairRecord.HostPrivateKey, pairRecord.HostCertificate);
}
catch (e) {
usbmux.close();
throw e;
}
}
/**
* Connects to a given port
*
* @param {string} udid Device UDID
* @param {number} port Port to connect
* @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed
* @returns {Promise<NodeJS.Socket|Object>} The socket or the object returned in the callback if the callback function exists
*/
async function connectPort(udid, port, socket = null) {
const usbmux = new usbmux_1.Usbmux(socket || await (0, usbmux_1.getDefaultSocket)());
try {
const device = await usbmux.findDevice(udid);
if (!device) {
throw new Error(`Could not find the expected device ${udid}`);
}
return await usbmux.connect(device.Properties.DeviceID, port, undefined);
}
catch (e) {
usbmux.close();
throw e;
}
}
/**
* Search developer image for device based on given repo. If certain file was found, this would automatic download,
* unzip and return the path of developer image and signature file.
*
* Developer images and signature files should be put into a zip file that matches this regular expression:
* `\d+\.\d+(\(([\w_|.()])+\))?.zip`. The image file should be named to `DeveloperDiskImage.dmg`,
* and the signature file should be named to `DeveloperDiskImage.dmg.signature`.
* Both files should be placed into same folder.
* @param {string} udid
* @param {import('./imagemounter/utils/list_developer_image').ImageFromGithubRepo} opts
* github repo option, `githubRepo` .
* @returns {Promise<import('./imagemounter/utils/list_developer_image').ImagePath>}
* @throws If opts error or failed on searching image
*/
async function fetchImageFromGithubRepo(udid, opts) {
const osVersion = await getOSVersion(udid);
if (!opts.githubRepo) {
throw new TypeError(`Mount option should contains local path or github repo, got ${JSON.stringify(opts)} instead`);
}
return await (0, list_developer_image_1.findDeveloperImage)(osVersion, opts);
}
/**
* Get maxFrameLength from environment variable or use default value
* @param {number} defaultSize - Default frame size in bytes
* @returns {number} Configured frame size in bytes
*/
function getMaxFrameLength(defaultSize) {
const envValue = process.env.APPIUM_IOS_MAX_FRAME_SIZE;
if (envValue) {
const parsed = lodash_1.default.parseInt(envValue, 10);
if (!isNaN(parsed) && parsed > 0) {
return parsed;
}
}
return defaultSize;
}
//# sourceMappingURL=utilities.js.map
;