UNPKG

appium-xcuitest-driver

Version:

Appium driver for iOS using XCUITest for backend

170 lines 6.89 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 remote_xpc_1 = require("./remote-xpc"); /** * Unified lockdown access for real devices. * * On iOS/tvOS 18+ attempts a tunnel registry RemoteXPC connection and lockdown over RSD * (`createLockdownServiceForTunnel`). When that path is unavailable, uses * {@linkcode utilities} from `appium-ios-device` (USB/local usbmux). */ class LockdownClient { udid; log; strategy; remoteXPCFacade; constructor(udid, log, strategy, remoteXPCFacade = null) { this.udid = udid; this.log = log; this.strategy = strategy; this.remoteXPCFacade = remoteXPCFacade; } /** * @param udid - Device UDID * @param opts - Creation options */ static async createForDevice(udid, opts = {}) { const { allowLegacyFallback = true, facade = null, logger = logger_1.log } = opts; if (!facade?.eligible) { if (!allowLegacyFallback) { throw new Error(`RemoteXPC lockdown is required for '${udid}', but this session is not eligible ` + `for RemoteXPC.`); } return new LockdownClient(udid, logger, 'ios-device'); } try { const strategy = await facade.resolveLockdownStrategy(); return new LockdownClient(udid, logger, strategy, facade); } catch (err) { if (!(0, remote_xpc_1.isRemoteXPCUnavailableError)(err)) { throw err; } if (!allowLegacyFallback) { throw err; } logger.warn('RemoteXPC lockdown is not available. Using appium-ios-device lockdown (legacy fallback).'); return new LockdownClient(udid, logger, 'ios-device'); } } 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() { // Tunnel strategy opens lockdown per operation; nothing to close here. } /** * 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'); } } requireRemoteXPCFacade() { if (!this.remoteXPCFacade) { throw new Error(`RemoteXPC lockdown is not configured for '${this.udid}'.`); } return this.remoteXPCFacade; } /** * 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) { return await this.requireRemoteXPCFacade().withUsbMuxLockdown(fn); } 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'; } async runWithTunnelLockdown(fn) { return await this.requireRemoteXPCFacade().withTunnelLockdown(fn); } } exports.LockdownClient = LockdownClient; //# sourceMappingURL=lockdown-client.js.map