UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

208 lines 8.44 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LockdownClient = void 0; const appium_ios_device_1 = require("appium-ios-device"); const logger_1 = require("../logger"); const utils_1 = require("../utils"); const remotexpc_utils_1 = require("./remotexpc-utils"); /** * Unified lockdown access for real devices. * * On iOS/tvOS 18+ attempts a tunnel registry RemoteXPC connection and lockdown over RSD * (`createLockdownServiceByTunnel`). When that path is unavailable, uses * {@linkcode utilities} from `appium-ios-device` (USB/local usbmux). */ class LockdownClient { udid; log; remotexpc; strategy; remoteXpcConnection; constructor(udid, log, remotexpc, strategy, remoteXpcConnection) { this.udid = udid; this.log = log; this.remotexpc = remotexpc; this.strategy = strategy; this.remoteXpcConnection = remoteXpcConnection; } /** * @param udid - Device UDID * @param opts - Driver options (used for iOS version gating) * @param log - Logger */ static async createForDevice(udid, opts, log = logger_1.log) { if (!(0, utils_1.isIos18OrNewer)(opts)) { return new LockdownClient(udid, log, null, 'ios-device', null); } const resolved = await (0, remotexpc_utils_1.tryGetRemoteXPCUsbMuxStrategy)(udid, log); if (!resolved) { const err = (0, remotexpc_utils_1.getLastRemoteXPCOptionalImportError)(); log.warn(`appium-ios-remotexpc unavailable for lockdown on '${udid}': ${err?.message ?? 'unknown'}. ` + `Using appium-ios-device lockdown (legacy fallback).`); return new LockdownClient(udid, log, null, 'ios-device', null); } const { remotexpc, useUsbMuxPath } = resolved; if (useUsbMuxPath) { return new LockdownClient(udid, log, remotexpc, 'remotexpc-usbmux', null); } if (typeof remotexpc.createLockdownServiceByTunnel !== 'function') { throw new Error(`appium-ios-remotexpc does not provide createLockdownServiceByTunnel for tunnel-only ` + `device '${udid}'. Please upgrade appium-ios-remotexpc.`); } const { remoteXPC } = await remotexpc.Services.createRemoteXPCConnection(udid); return new LockdownClient(udid, log, remotexpc, 'remotexpc-tunnel', remoteXPC); } static coerceFiniteNumber(value) { if (typeof value === 'number' && Number.isFinite(value)) { return value; } if (typeof value === 'bigint') { const converted = Number(value); return Number.isFinite(converted) ? converted : undefined; } if (typeof value === 'string') { const parsed = Number(value); if (Number.isFinite(parsed)) { return parsed; } } return undefined; } async close() { if (this.remoteXpcConnection) { try { await this.remoteXpcConnection.close(); } catch { // ignore } } } /** * Full lockdown `GetValue` payload (`GetValue` with no key/domain). */ async getDeviceInfo() { if (this.strategy === 'ios-device') { return await appium_ios_device_1.utilities.getDeviceInfo(this.udid); } return (await this.runWithRemotexpcLockdownRequiringValue((lockdown) => lockdown.getDeviceInfo(), 'device info payload')); } /** * Device ProductVersion from lockdown. * * Uses the same lockdown selection strategy as {@linkcode getDeviceInfo}. * If a RemoteXPC lockdown payload does not include ProductVersion, throws. */ async getOSVersion() { if (this.strategy === 'ios-device') { return await appium_ios_device_1.utilities.getOSVersion(this.udid); } return await this.runWithRemotexpcLockdownRequiringValue((lockdown) => lockdown.getProductVersion(), 'ProductVersion'); } /** * Fields needed to format device local time (same contract as {@linkcode utilities.getDeviceTime}). */ async getDeviceTimeFields() { const readTimeFromLockdown = async (lockdown) => { const info = await lockdown.getDeviceInfo(); const timestamp = LockdownClient.coerceFiniteNumber(info.TimeIntervalSince1970); const tzOffsetSeconds = LockdownClient.coerceFiniteNumber(info.TimeZoneOffsetFromUTC); if (timestamp === undefined || tzOffsetSeconds === undefined) { return undefined; } return { timestamp, utcOffset: tzOffsetSeconds / 60 }; }; switch (this.strategy) { case 'ios-device': { const { timestamp, utcOffset, timeZone } = await appium_ios_device_1.utilities.getDeviceTime(this.udid); return { timestamp, utcOffset: this.normalizeUtcOffsetMinutes(utcOffset, timeZone), }; } case 'remotexpc-usbmux': case 'remotexpc-tunnel': return await this.runWithRemotexpcLockdownRequiringValue(readTimeFromLockdown, 'device time fields'); } } /** * Legacy ios-device can provide inconsistent offset payloads. Normalize to a final offset in * minutes for consumers. */ normalizeUtcOffsetMinutes(utcOffset, timeZone) { // Normal/expected: offset already in minutes. if (Math.abs(utcOffset) <= 12 * 60) { return utcOffset; } // Sometimes `timeZone` is a numeric offset in seconds. const offsetSeconds = typeof timeZone === 'number' ? timeZone : Number(timeZone); if (Number.isFinite(offsetSeconds) && Math.abs(offsetSeconds) <= 12 * 60 * 60) { return offsetSeconds / 60; } this.log.warn(`Did not know how to apply UTC offset from lockdown (utcOffset=${utcOffset}, timeZone=${timeZone}). ` + `Using UTC.`); return 0; } async runWithRemotexpcUsbmuxLockdown(fn) { if (!this.remotexpc) { throw new Error(`appium-ios-remotexpc module is not initialized for '${this.udid}'.`); } try { const { lockdownService } = await this.remotexpc.createLockdownServiceByUDID(this.udid); try { return await fn(lockdownService); } finally { lockdownService.close(); } } catch (err) { throw new Error(`Failed to read lockdown via appium-ios-remotexpc USBMUX path for '${this.udid}': ` + `${err.message}`, { cause: err }); } } async runWithRemotexpcLockdown(fn) { switch (this.strategy) { case 'remotexpc-usbmux': return await this.runWithRemotexpcUsbmuxLockdown(fn); case 'remotexpc-tunnel': return await this.runWithTunnelLockdown(fn); default: throw new Error(`RemoteXPC lockdown is not active for '${this.udid}'.`); } } async runWithRemotexpcLockdownRequiringValue(fn, valueName) { const value = await this.runWithRemotexpcLockdown(fn); if (!value) { throw new Error(`RemoteXPC ${this.getRemotexpcLockdownLabel()} lockdown did not return ${valueName} for '${this.udid}'.`); } return value; } getRemotexpcLockdownLabel() { return this.strategy === 'remotexpc-usbmux' ? 'USB' : 'tunnel'; } /** * Runs an operation with lockdown over the RSD tunnel. */ async runWithTunnelLockdown(fn) { if (!this.remotexpc || !this.remoteXpcConnection) { throw new Error(`RemoteXPC tunnel is not initialized for '${this.udid}'.`); } try { const lockdown = await this.remotexpc.createLockdownServiceByTunnel(this.remoteXpcConnection, this.udid); try { return await fn(lockdown); } finally { lockdown.close(); } } catch (err) { throw new Error(`Tunnel lockdown failed for '${this.udid}': ${err.message}`, { cause: err, }); } } } exports.LockdownClient = LockdownClient; //# sourceMappingURL=lockdown-client.js.map