appium-ios-device
Version:
Appium API for dealing with iOS devices
284 lines • 11.6 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Usbmux = void 0;
exports.getDefaultSocket = getDefaultSocket;
const net_1 = __importDefault(require("net"));
const os_1 = __importDefault(require("os"));
const lodash_1 = __importDefault(require("lodash"));
const bluebird_1 = __importDefault(require("bluebird"));
const support_1 = require("@appium/support");
const length_based_splitter_1 = require("../util/transformer/length-based-splitter");
const usbmux_decoder_js_1 = require("./transformer/usbmux-decoder.js");
const usbmux_encoder_js_1 = require("./transformer/usbmux-encoder.js");
const path_1 = __importDefault(require("path"));
const plist_service_1 = require("../plist-service");
const lockdown_1 = require("../lockdown");
const base_service_1 = require("../base-service");
const utilities_1 = require("../utilities");
const constants_1 = require("../constants");
const logger_1 = require("../logger");
const MAX_FRAME_SIZE = 1 * constants_1.MB;
const USBMUX_RESULT = {
OK: 0,
BADCOMMAND: 1,
BADDEV: 2,
CONNREFUSED: 3,
};
let name, version;
try {
// first try assuming this is in the `build` folder
({ name, version } = require(path_1.default.resolve(__dirname, '..', '..', '..', 'package.json')));
}
catch {
// then try assuming it is not
({ name, version } = require(path_1.default.resolve(__dirname, '..', '..', 'package.json')));
}
const DEFAULT_USBMUXD_SOCKET = '/var/run/usbmuxd';
const DEFAULT_USBMUXD_PORT = 27015;
const DEFAULT_USBMUXD_HOST = '127.0.0.1';
const PROG_NAME = name;
const CLIENT_VERSION_STRING = `${name}-${version}`;
function swap16(val) {
return ((val & 0xFF) << 8) | ((val >> 8) & 0xFF);
}
/**
* @typedef {Object} SocketOptions
* @property {string?} socketPath [/var/run/usbmuxd] The full path to the usbmuxd Unix socket
* @property {number?} socketPort [27015] The port number to connect to if running on Windows
* or in WSL1 mode
* @property {string?} socketHost [127.0.0.1] The host name to connect to if running on Windows
* or in WSL1 mode
* @property {number?} timeout [5000] The number of milliseconds to wait until
* the socket is connected
*/
/**
* Connects a socket to usbmuxd service
*
* @param {Partial<SocketOptions>} opts
* @throws {Error} If there was an error while accessing the socket or
* a connection error happened
* @throws {B.TimeoutError} if connection timeout happened
* @returns {Promise<NodeJS.Socket>} Connected socket instance
*/
async function getDefaultSocket(opts = {}) {
const defaults = {
socketPath: DEFAULT_USBMUXD_SOCKET,
socketPort: DEFAULT_USBMUXD_PORT,
socketHost: DEFAULT_USBMUXD_HOST,
timeout: 5000
};
if (process.env.USBMUXD_SOCKET_ADDRESS && !opts.socketPath && !opts.socketPort && !opts.socketHost) {
logger_1.log.debug(`Using USBMUXD_SOCKET_ADDRESS environment variable as default socket: ${process.env.USBMUXD_SOCKET_ADDRESS}`);
// "unix:" or "UNIX:" prefix is optional for unix socket paths.
const usbmuxdSocketAddress = process.env.USBMUXD_SOCKET_ADDRESS.replace(/^(unix):/i, '');
const [ip, port] = usbmuxdSocketAddress.split(':');
if (ip && port) {
defaults.socketHost = ip;
defaults.socketPort = parseInt(port, 10);
}
else {
defaults.socketPath = usbmuxdSocketAddress;
}
}
const { socketPath, socketPort, socketHost, timeout } = { ...defaults, ...opts };
let socket;
if (await support_1.fs.exists(socketPath ?? '')) {
socket = net_1.default.createConnection(socketPath ?? '');
}
else if (process.platform === 'win32'
|| (process.platform === 'linux' && /microsoft/i.test(os_1.default.release()))) {
// Connect to usbmuxd when running on WSL1
// @ts-ignore socketPort should be what we need
socket = net_1.default.createConnection(socketPort, socketHost);
}
else {
throw new Error(`The usbmuxd socket at '${socketPath}' does not exist or is not accessible`);
}
return await new bluebird_1.default((resolve, reject) => {
socket.once('error', reject);
socket.once('connect', () => resolve(socket));
}).timeout(timeout ?? 5000);
}
class Usbmux extends base_service_1.BaseServiceSocket {
constructor(socketClient) {
super(socketClient);
this._decoder = new usbmux_decoder_js_1.UsbmuxDecoder();
this._splitter = new length_based_splitter_1.LengthBasedSplitter({
readableStream: socketClient,
littleEndian: true,
maxFrameLength: (0, utilities_1.getMaxFrameLength)(MAX_FRAME_SIZE),
lengthFieldOffset: 0,
lengthFieldLength: 4,
lengthAdjustment: 0,
});
this._socketClient.pipe(this._splitter).pipe(this._decoder);
this._encoder = new usbmux_encoder_js_1.UsbmuxEncoder();
this._encoder.pipe(this._socketClient);
this._assignClientFailureHandlers(this._encoder);
this._tag = 0;
this._responseCallbacks = {};
this._decoder.on('data', this._handleData.bind(this));
}
_handleData(data) {
const cb = this._responseCallbacks[data.header.tag] || lodash_1.default.noop;
cb(data); // eslint-disable-line promise/prefer-await-to-callbacks
}
/**
* Returns the BUID of the host computer from usbmuxd
* @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd
* @returns {Promise<string>}
*/
async readBUID(timeout = 5000) {
const { tag, receivePromise } = this._receivePlistPromise(timeout, (data) => {
if (data.payload.BUID) {
return data.payload.BUID;
}
throw new Error(`Unexpected data: ${JSON.stringify(data)}`);
});
this._sendPlist({
tag,
payload: {
MessageType: 'ReadBUID',
ProgName: PROG_NAME,
ClientVersionString: CLIENT_VERSION_STRING
}
});
return await receivePromise;
}
/**
* Reads the pair record of a device. It will return null if it doesn't exists
* @param {string} udid the udid of the device
* @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd
* @returns {Promise<any?>}
*/
async readPairRecord(udid, timeout = 5000) {
const { tag, receivePromise } = this._receivePlistPromise(timeout, (data) => {
if (!data.payload.PairRecordData) {
return null;
}
try {
return support_1.plist.parsePlist(data.payload.PairRecordData);
}
catch {
throw new Error(`Unexpected data: ${JSON.stringify(data)}`);
}
});
this._sendPlist({
tag,
payload: {
MessageType: 'ReadPairRecord',
PairRecordID: udid,
ProgName: PROG_NAME,
ClientVersionString: CLIENT_VERSION_STRING
}
});
return await receivePromise;
}
_sendPlist(json) {
this._encoder.write(json);
}
_receivePlistPromise(timeout = 5000, responseCallback) {
const tag = this._tag++;
const receivePromise = new bluebird_1.default((resolve, reject) => {
this._responseCallbacks[tag] = (data) => {
try {
resolve(responseCallback(data));
}
catch (e) {
reject(e);
}
};
setTimeout(() => reject(new Error(`Failed to receive any data within the timeout: ${timeout}`)), timeout);
});
return { tag, receivePromise };
}
/**
* Lists all devices connected to the host
* @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd
* @returns {Promise<any[]>}
*/
async listDevices(timeout = 5000) {
const { tag, receivePromise } = this._receivePlistPromise(timeout, (data) => {
if (data.payload.DeviceList) {
return data.payload.DeviceList;
}
throw new Error(`Unexpected data: ${JSON.stringify(data)}`);
});
this._sendPlist({
tag,
payload: {
MessageType: 'ListDevices',
ProgName: PROG_NAME,
ClientVersionString: CLIENT_VERSION_STRING
}
});
return await receivePromise;
}
/**
* Looks for a device with the passed udid. It will return undefined if the device is not found
* @param {string} udid the udid of the device
* @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd
* @returns {Promise<any?>}
*/
async findDevice(udid, timeout = 5000) {
const devices = await this.listDevices(timeout);
return lodash_1.default.find(devices, (device) => device.Properties.SerialNumber === udid);
}
/**
* Connects to the lockdownd on the device and returns a Lockdown client
* @param {string} udid the udid of the device
* @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd
* @returns {Promise<Lockdown>}
*/
async connectLockdown(udid, timeout = 5000) {
const device = await this.findDevice(udid, timeout);
if (!device) {
throw new Error(`Could not find the expected device '${udid}'`);
}
const socket = await this.connect(device.Properties.DeviceID, lockdown_1.LOCKDOWN_PORT, timeout);
return new lockdown_1.Lockdown(new plist_service_1.PlistService(socket));
}
/**
* Connects to a certain port on the device
* @param {string} deviceID the device id which can be retrieved from the properties of a device
* @param {number} port the port number that wants to be connected
* @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd
* @returns {Promise<net.Socket|Object>} The socket or the object returned in the callback if the callback function exists
*/
async connect(deviceID, port, timeout = 5000) {
const { tag, receivePromise } = this._receivePlistPromise(timeout, (data) => {
if (data.payload.MessageType !== 'Result') {
throw new Error(`Unexpected data: ${JSON.stringify(data)}`);
}
if (data.payload.Number === USBMUX_RESULT.OK) {
this._splitter.shutdown();
this._socketClient.unpipe(this._splitter);
this._splitter.unpipe(this._decoder);
return this._socketClient;
}
else if (data.payload.Number === USBMUX_RESULT.CONNREFUSED) {
throw new Error(`Connection was refused to port ${port}`);
}
else {
throw new Error(`Unexpected data: ${JSON.stringify(data)}`);
}
});
this._sendPlist({
tag,
payload: {
MessageType: 'Connect',
ProgName: PROG_NAME,
ClientVersionString: CLIENT_VERSION_STRING,
DeviceID: deviceID,
PortNumber: swap16(port)
}
});
return await receivePromise;
}
}
exports.Usbmux = Usbmux;
exports.default = Usbmux;
//# sourceMappingURL=index.js.map
;