"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClientManager = void 0; const stream_1 = require("stream"); const tls = require("tls"); const afc_1 = require("./client/afc"); const debugserver_1 = require("./client/debugserver"); const installation_proxy_1 = require("./client/installation_proxy"); const lockdownd_1 = require("./client/lockdownd"); const mobile_image_mounter_1 = require("./client/mobile_image_mounter"); const usbmuxd_1 = require("./client/usbmuxd"); class ClientManager { constructor(pairRecord, device, lockdowndClient) { this.pairRecord = pairRecord; this.device = device; this.lockdowndClient = lockdowndClient; this.connections = [lockdowndClient.socket]; } static async create(udid) { const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket()); const device = await usbmuxClient.getDevice(udid); const pairRecord = await usbmuxClient.readPairRecord(device.Properties.SerialNumber); const lockdownSocket = await usbmuxClient.connect(device, 62078); const lockdownClient = new lockdownd_1.LockdowndClient(lockdownSocket); await lockdownClient.doHandshake(pairRecord); return new ClientManager(pairRecord, device, lockdownClient); } async getUsbmuxdClient() { const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket()); this.connections.push(usbmuxClient.socket); return usbmuxClient; } async getLockdowndClient() { const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket()); const lockdownSocket = await usbmuxClient.connect(this.device, 62078); const lockdownClient = new lockdownd_1.LockdowndClient(lockdownSocket); this.connections.push(lockdownClient.socket); return lockdownClient; } async getLockdowndClientWithHandshake() { const lockdownClient = await this.getLockdowndClient(); await lockdownClient.doHandshake(this.pairRecord); return lockdownClient; } async getAFCClient() { return this.getServiceClient('com.apple.afc', afc_1.AFCClient); } async getInstallationProxyClient() { return this.getServiceClient('com.apple.mobile.installation_proxy', installation_proxy_1.InstallationProxyClient); } async getMobileImageMounterClient() { return this.getServiceClient('com.apple.mobile.mobile_image_mounter', mobile_image_mounter_1.MobileImageMounterClient); } async getDebugserverClient() { try { // iOS 14 added support for a secure debug service so try to connect to that first return await this.getServiceClient('com.apple.debugserver.DVTSecureSocketProxy', debugserver_1.DebugserverClient); } catch { // otherwise, fall back to the previous implementation return this.getServiceClient('com.apple.debugserver', debugserver_1.DebugserverClient, true); } } async getServiceClient(name, ServiceType, disableSSL = false) { const { port: servicePort, enableServiceSSL } = await this.lockdowndClient.startService(name); const usbmuxClient = new usbmuxd_1.UsbmuxdClient(usbmuxd_1.UsbmuxdClient.connectUsbmuxdSocket()); let usbmuxdSocket = await usbmuxClient.connect(this.device, servicePort); if (enableServiceSSL) { const tlsOptions = { rejectUnauthorized: false, secureContext: tls.createSecureContext({ secureProtocol: 'TLSv1_2_method', cert: this.pairRecord.RootCertificate, key: this.pairRecord.RootPrivateKey, }), }; // Some services seem to not support TLS/SSL after the initial handshake // More info: https://github.com/libimobiledevice/libimobiledevice/issues/793 if (disableSSL) { // According to https://nodejs.org/api/tls.html#tls_tls_connect_options_callback we can // pass any Duplex in to tls.connect instead of a Socket. So we'll use our proxy to keep // the TLS wrapper and underlying usbmuxd socket separate. const proxy = new UsbmuxdProxy(usbmuxdSocket); tlsOptions.socket = proxy; await new Promise((res, rej) => { const timeoutId = setTimeout(() => { rej('The TLS handshake failed to complete after 5s.'); }, 5000); tls.connect(tlsOptions, function () { clearTimeout(timeoutId); // After the handshake, we don't need TLS or the proxy anymore, // since we'll just pass in the naked usbmuxd socket to the service client this.destroy(); res(); }); }); } else { tlsOptions.socket = usbmuxdSocket; usbmuxdSocket = tls.connect(tlsOptions); } } const client = new ServiceType(usbmuxdSocket); this.connections.push(client.socket); return client; } end() { for (const socket of this.connections) { // may already be closed try { socket.end(); } catch (err) { // ignore } } } } exports.ClientManager = ClientManager; class UsbmuxdProxy extends stream_1.Duplex { constructor(usbmuxdSock) { super(); this.usbmuxdSock = usbmuxdSock; this.usbmuxdSock.on('data', (data) => { this.push(data); }); } _write(chunk, encoding, callback) { this.usbmuxdSock.write(chunk); callback(); } _read(size) { // Stub so we don't error, since we push everything we get from usbmuxd as it comes in. // TODO: better way to do this? } _destroy() { this.usbmuxdSock.removeAllListeners(); } }