@mcesystems/apple-kit 1.0.60 → 1.0.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -32389,16 +32389,91 @@ async function getActivationState(udid) {
32389
32389
  }
32390
32390
 
32391
32391
  // src/logic/actions/device.ts
32392
- var import_node_fs3 = require("node:fs");
32393
- var import_node_path7 = require("node:path");
32392
+ var import_node_fs2 = require("node:fs");
32393
+ var import_node_path3 = require("node:path");
32394
32394
 
32395
- // src/logic/activationFlow.ts
32396
- var import_promises2 = require("node:fs/promises");
32397
- var import_node_os2 = require("node:os");
32398
- var import_node_path6 = require("node:path");
32395
+ // src/logic/dataParser.ts
32396
+ function parsePlistOutput(output) {
32397
+ const result = {};
32398
+ const lines = output.split("\n");
32399
+ for (const line of lines) {
32400
+ const colonIndex = line.indexOf(":");
32401
+ if (colonIndex > 0) {
32402
+ const key = line.substring(0, colonIndex).trim();
32403
+ const value = line.substring(colonIndex + 1).trim();
32404
+ result[key] = value;
32405
+ }
32406
+ }
32407
+ return result;
32408
+ }
32409
+ function parseAppList(output) {
32410
+ const apps = [];
32411
+ const lines = output.trim().split("\n");
32412
+ for (const line of lines) {
32413
+ const match = line.match(/^([^,]+),\s*([^,]+),\s*"?([^"]+)"?/);
32414
+ if (match) {
32415
+ apps.push({
32416
+ bundleId: match[1].trim(),
32417
+ version: match[2].trim(),
32418
+ displayName: match[3].trim(),
32419
+ bundleVersion: ""
32420
+ });
32421
+ }
32422
+ }
32423
+ return apps;
32424
+ }
32425
+
32426
+ // src/logic/actions/device.ts
32427
+ async function getDeviceInfo(udid) {
32428
+ logTask(`Getting device info for ${udid}`);
32429
+ const result = await runIDeviceTool("ideviceinfo", ["-u", udid]);
32430
+ if (!result) {
32431
+ throw new Error("Failed to get device info");
32432
+ }
32433
+ const props = parsePlistOutput(result.stdout);
32434
+ return {
32435
+ deviceName: props.DeviceName || "",
32436
+ productType: props.ProductType || "",
32437
+ productVersion: props.ProductVersion || "",
32438
+ buildVersion: props.BuildVersion || "",
32439
+ serialNumber: props.SerialNumber || "",
32440
+ udid: props.UniqueDeviceID || udid,
32441
+ wifiAddress: props.WiFiAddress || "",
32442
+ bluetoothAddress: props.BluetoothAddress || "",
32443
+ phoneNumber: props.PhoneNumber || "",
32444
+ cpuArchitecture: props.CPUArchitecture || "",
32445
+ hardwareModel: props.HardwareModel || "",
32446
+ modelNumber: props.ModelNumber || "",
32447
+ regionInfo: props.RegionInfo || "",
32448
+ timeZone: props.TimeZone || "",
32449
+ uniqueChipID: props.UniqueChipID || "",
32450
+ isPaired: true
32451
+ // If we can get info, device is paired
32452
+ };
32453
+ }
32454
+ async function info(udid, iosCli2) {
32455
+ logTask(`Getting device info for ${udid}`);
32456
+ const result = await iosCli2.info(udid);
32457
+ if (!result) {
32458
+ throw new Error("Failed to get device info");
32459
+ }
32460
+ return result.output[0];
32461
+ }
32462
+ async function wipeDevice(udid, iosCli2) {
32463
+ logTask(`Wiping device ${udid}`);
32464
+ await iosCli2.wipe(udid);
32465
+ }
32466
+ async function removeProfile(profileIdentifier, udid, iosCli2) {
32467
+ logTask(`Removing profile ${profileIdentifier} from device ${udid}`);
32468
+ await iosCli2.removeProfile(udid, profileIdentifier);
32469
+ }
32470
+ async function listProfiles(udid, iosCli2) {
32471
+ logTask(`Listing profiles for device ${udid}`);
32472
+ return iosCli2.listProfiles(udid);
32473
+ }
32399
32474
 
32400
32475
  // src/utils/mdmClient.ts
32401
- var import_node_path3 = require("node:path");
32476
+ var import_node_path4 = require("node:path");
32402
32477
 
32403
32478
  // ../../node_modules/.pnpm/graphql-request@5.2.0_encoding@0.1.13_graphql@14.7.0/node_modules/graphql-request/build/esm/defaultJsonSerializer.js
32404
32479
  var defaultJsonSerializer = {
@@ -33168,7 +33243,7 @@ function createMdmClient(config) {
33168
33243
  }
33169
33244
  async function createMDMClient() {
33170
33245
  const endpoint = process.env.MDM_ENDPOINT;
33171
- const credPath = (0, import_node_path3.join)(getResourcesPath(), "@clientLocal.json");
33246
+ const credPath = (0, import_node_path4.join)(getResourcesPath(), "@clientLocal.json");
33172
33247
  if (!endpoint || !credPath) {
33173
33248
  return void 0;
33174
33249
  }
@@ -33181,111 +33256,317 @@ async function createMDMClient() {
33181
33256
  });
33182
33257
  }
33183
33258
 
33184
- // src/utils/wifiProfile.ts
33185
- var import_node_crypto2 = require("node:crypto");
33186
-
33187
- // src/utils/templateLoader.ts
33188
- var import_node_fs2 = require("node:fs");
33189
- var import_promises = require("node:fs/promises");
33190
- var import_node_path4 = require("node:path");
33191
- var import_node_url = require("node:url");
33192
- var import_meta = {};
33193
- function resolveEnvPlistDir() {
33194
- const envPath = (0, import_node_path4.join)(getResourcesPath(), "plist");
33195
- if (!(0, import_node_fs2.existsSync)(envPath)) {
33196
- return null;
33197
- }
33198
- const absolutePath = (0, import_node_path4.isAbsolute)(envPath) ? envPath : (0, import_node_path4.join)(process.cwd(), envPath);
33199
- if ((0, import_node_fs2.existsSync)(absolutePath)) {
33200
- return absolutePath;
33259
+ // src/logic/actions/pair.ts
33260
+ async function isPaired(udid) {
33261
+ logTask(`Checking pairing status for ${udid}`);
33262
+ try {
33263
+ const result = await runIDeviceTool("idevicepair", ["-u", udid, "validate"]);
33264
+ if (!result) {
33265
+ return false;
33266
+ }
33267
+ return result.stdout.toLowerCase().includes("success");
33268
+ } catch {
33269
+ return false;
33201
33270
  }
33202
- return null;
33203
33271
  }
33204
- function getPlistDir(overrideDir) {
33205
- if (overrideDir) {
33206
- return (0, import_node_path4.resolve)(overrideDir);
33207
- }
33208
- const envPlistDir = resolveEnvPlistDir();
33209
- if (envPlistDir) {
33210
- return envPlistDir;
33272
+ async function trustDevice(udid, timeout2 = 6e4, onWaitingForTrust) {
33273
+ logTask(`Trusting device ${udid}`);
33274
+ if (await isPaired(udid)) {
33275
+ logInfo(`Device ${udid} is already trusted`);
33276
+ return true;
33211
33277
  }
33212
- let baseDir;
33278
+ logInfo(`Initiating pairing for device ${udid}`);
33213
33279
  try {
33214
- if (typeof import_meta !== "undefined" && import_meta.url) {
33215
- const currentFile = (0, import_node_url.fileURLToPath)(import_meta.url);
33216
- baseDir = (0, import_node_path4.dirname)(currentFile);
33217
- } else {
33218
- baseDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
33280
+ const pairResult = await pair(udid);
33281
+ if (pairResult) {
33282
+ logInfo(`Device ${udid} paired successfully`);
33283
+ return true;
33219
33284
  }
33220
- } catch {
33221
- baseDir = process.cwd();
33285
+ } catch (error) {
33286
+ logError(`Pairing failed with ${udid}: ${error}`);
33222
33287
  }
33223
- const sourcePlistDir = (0, import_node_path4.join)(baseDir, "../plist");
33224
- if ((0, import_node_fs2.existsSync)(sourcePlistDir)) {
33225
- return sourcePlistDir;
33288
+ logInfo("Please accept the trust dialog on the device...");
33289
+ onWaitingForTrust?.();
33290
+ try {
33291
+ await waitForPairing(udid, timeout2, 1e3);
33292
+ logInfo(`Device ${udid} is now trusted`);
33293
+ return true;
33294
+ } catch {
33295
+ logInfo(`Timeout waiting for trust acceptance on device ${udid}`);
33296
+ return false;
33226
33297
  }
33227
- const distPlistDir = (0, import_node_path4.join)(baseDir, "../plist");
33228
- if ((0, import_node_fs2.existsSync)(distPlistDir)) {
33229
- return distPlistDir;
33298
+ }
33299
+ async function waitForPairing(udid, timeout2 = 12e4, pollInterval = 1e3) {
33300
+ logTask(`Waiting for pairing on device ${udid}`);
33301
+ const startTime = Date.now();
33302
+ while (Date.now() - startTime < timeout2) {
33303
+ if (await isPaired(udid)) {
33304
+ logInfo(`Device ${udid} is now paired`);
33305
+ return true;
33306
+ }
33307
+ await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
33230
33308
  }
33231
- const distPlistDir2 = (0, import_node_path4.join)(baseDir, "../dist/plist");
33232
- if ((0, import_node_fs2.existsSync)(distPlistDir2)) {
33233
- return distPlistDir2;
33309
+ throw new Error(`Timeout waiting for device pairing after ${timeout2}ms`);
33310
+ }
33311
+ async function pair(udid) {
33312
+ logTask(`Initiating pairing for device ${udid}`);
33313
+ try {
33314
+ const result = await runIDeviceTool("idevicepair", ["-u", udid, "pair"]);
33315
+ if (!result) {
33316
+ return false;
33317
+ }
33318
+ return result.stdout.toLowerCase().includes("success");
33319
+ } catch (error) {
33320
+ const errorMsg = error instanceof Error ? error.message : String(error);
33321
+ if (errorMsg.includes("Please accept the trust dialog")) {
33322
+ return false;
33323
+ }
33324
+ throw error;
33234
33325
  }
33235
- const srcPlistFallback = (0, import_node_path4.join)(process.cwd(), "src/plist");
33236
- if ((0, import_node_fs2.existsSync)(srcPlistFallback)) {
33237
- return srcPlistFallback;
33326
+ }
33327
+ async function unpair(udid) {
33328
+ logTask(`Un-pairing device ${udid}`);
33329
+ try {
33330
+ const result = await runIDeviceTool("idevicepair", ["-u", udid, "unpair"]);
33331
+ if (!result) {
33332
+ return false;
33333
+ }
33334
+ return result.stdout.toLowerCase().includes("success");
33335
+ } catch {
33336
+ return false;
33238
33337
  }
33239
- const distPlistFallback = (0, import_node_path4.join)(process.cwd(), "dist/plist");
33240
- if ((0, import_node_fs2.existsSync)(distPlistFallback)) {
33241
- return distPlistFallback;
33338
+ }
33339
+
33340
+ // src/logic/actions/install.ts
33341
+ async function installLocalApp(ipaPath, udid) {
33342
+ logTask(`Installing app ${ipaPath} on device ${udid}`);
33343
+ await runIDeviceTool("ideviceinstaller", ["-u", udid, "install", ipaPath]);
33344
+ }
33345
+ async function installManagedApp(ipaPath, udid, options) {
33346
+ await installLocalApp(ipaPath, udid);
33347
+ const client = await createMDMClient();
33348
+ logTask("Installing app via MDM for management takeover");
33349
+ if (client) {
33350
+ client.installApp(udid, options);
33242
33351
  }
33243
- return (0, import_node_path4.join)(process.cwd(), "src/plist");
33244
33352
  }
33245
- async function loadTemplate(templateName, plistDir) {
33246
- const templatePath = (0, import_node_path4.join)(getPlistDir(plistDir), templateName);
33247
- const content = await (0, import_promises.readFile)(templatePath, "utf-8");
33248
- return content;
33353
+ async function uninstallApp(bundleId, udid) {
33354
+ logTask(`Uninstalling app ${bundleId} from device ${udid}`);
33355
+ if (!await isPaired(udid)) {
33356
+ await waitForPairing(udid, 1e4);
33357
+ }
33358
+ await runIDeviceTool("ideviceinstaller", ["-u", udid, "uninstall", bundleId]);
33249
33359
  }
33250
- function processTemplate(template, variables) {
33251
- let result = template;
33252
- result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_match, key, content) => {
33253
- const value = variables[key];
33254
- if (value && value !== false && value !== "" && value !== null && value !== void 0) {
33255
- return processTemplate(content, variables);
33256
- }
33257
- return "";
33258
- });
33259
- result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
33260
- const value = variables[key];
33261
- if (value === void 0 || value === null) {
33262
- return "";
33263
- }
33264
- if (typeof value === "boolean") {
33265
- return value ? "true" : "false";
33360
+ async function listApps(udid) {
33361
+ logTask(`Listing apps on device ${udid}`);
33362
+ if (!await isPaired(udid)) {
33363
+ await waitForPairing(udid, 1e4);
33364
+ }
33365
+ try {
33366
+ const result = await runIDeviceTool("ideviceinstaller", ["-u", udid, "list"]);
33367
+ if (!result) {
33368
+ return [];
33266
33369
  }
33267
- return escapeXml(String(value));
33268
- });
33269
- return result;
33370
+ const { stdout } = result;
33371
+ return parseAppList(stdout);
33372
+ } catch {
33373
+ return [];
33374
+ }
33270
33375
  }
33271
- function escapeXml(str) {
33272
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
33376
+ async function isAppInstalled(bundleId, udid) {
33377
+ logTask(`Checking if app ${bundleId} is installed on device ${udid}`);
33378
+ const apps = await listApps(udid);
33379
+ return apps.some((app) => app.bundleId === bundleId);
33380
+ }
33381
+ async function launchApp(bundleId, _args, udid) {
33382
+ logTask(`Launching app ${bundleId} on device ${udid}`);
33273
33383
  }
33274
33384
 
33275
- // src/utils/wifiProfile.ts
33276
- async function generateWifiProfile(config, options) {
33277
- const {
33278
- ssid,
33279
- password,
33280
- encryptionType = "WPA2",
33281
- autoJoin = true,
33282
- hiddenNetwork = false,
33283
- organizationName = "MCE Systems",
33284
- displayName = `WiFi - ${ssid}`,
33285
- enterprise = false,
33286
- username,
33287
- eapType = "PEAP",
33288
- acceptAnyCertificate = true
33385
+ // src/logic/actions/proxy.ts
33386
+ var import_node_child_process2 = require("node:child_process");
33387
+ var import_node_path5 = require("node:path");
33388
+ function startPortForward(localPort, devicePort, udid, startupTimeout = 500) {
33389
+ return new Promise((resolve2, reject) => {
33390
+ logTask(`Starting port forward ${localPort} -> ${devicePort} for device ${udid}`);
33391
+ const binPath = getResourcesBinPath();
33392
+ const ext = process.platform === "win32" ? ".exe" : "";
33393
+ const toolPath = binPath ? (0, import_node_path5.join)(binPath, `iproxy${ext}`) : `iproxy${ext}`;
33394
+ const spawnOptions = {
33395
+ windowsHide: true,
33396
+ stdio: ["ignore", "pipe", "pipe"]
33397
+ };
33398
+ if (binPath) {
33399
+ spawnOptions.cwd = binPath;
33400
+ }
33401
+ logInfo(`Spawning iproxy with path: ${toolPath}`);
33402
+ logInfo(`Arguments: ${[localPort.toString(), devicePort.toString(), "-u", udid].join(" ")}`);
33403
+ logInfo(`Options: ${JSON.stringify(spawnOptions)}`);
33404
+ const child = (0, import_node_child_process2.spawn)(
33405
+ toolPath,
33406
+ [localPort.toString(), devicePort.toString(), "-u", udid],
33407
+ spawnOptions
33408
+ );
33409
+ let hasResolved = false;
33410
+ let stderrOutput = "";
33411
+ child.stdout?.on("data", (data) => {
33412
+ logTask(`${data.toString()}`);
33413
+ });
33414
+ child.stderr?.on("data", (data) => {
33415
+ logError(`${data.toString()}`);
33416
+ const msg = data.toString();
33417
+ stderrOutput += msg;
33418
+ if (msg.toLowerCase().includes("error") && !hasResolved) {
33419
+ hasResolved = true;
33420
+ child.kill();
33421
+ reject(new Error(`Port forwarding failed: ${msg}`));
33422
+ }
33423
+ });
33424
+ child.on("error", (error) => {
33425
+ if (!hasResolved) {
33426
+ hasResolved = true;
33427
+ reject(new Error(`Failed to start iproxy: ${error.message}`));
33428
+ }
33429
+ });
33430
+ child.on("exit", (code) => {
33431
+ if (!hasResolved) {
33432
+ hasResolved = true;
33433
+ if (code !== 0 && code !== null) {
33434
+ reject(new Error(`iproxy exited with code ${code}: ${stderrOutput}`));
33435
+ }
33436
+ }
33437
+ });
33438
+ setTimeout(() => {
33439
+ if (!hasResolved) {
33440
+ hasResolved = true;
33441
+ logTask(`Port forward started: ${localPort} -> ${devicePort} for device ${udid}`);
33442
+ resolve2({
33443
+ result: {
33444
+ localPort,
33445
+ devicePort
33446
+ },
33447
+ process: child
33448
+ });
33449
+ }
33450
+ }, startupTimeout);
33451
+ });
33452
+ }
33453
+ function killPortForwardProcess(process2) {
33454
+ if (process2 && !process2.killed) {
33455
+ logTask("Killing port forward process");
33456
+ process2.kill();
33457
+ }
33458
+ }
33459
+
33460
+ // src/logic/activationFlow.ts
33461
+ var import_promises2 = require("node:fs/promises");
33462
+ var import_node_os2 = require("node:os");
33463
+ var import_node_path7 = require("node:path");
33464
+
33465
+ // src/utils/wifiProfile.ts
33466
+ var import_node_crypto2 = require("node:crypto");
33467
+
33468
+ // src/utils/templateLoader.ts
33469
+ var import_node_fs3 = require("node:fs");
33470
+ var import_promises = require("node:fs/promises");
33471
+ var import_node_path6 = require("node:path");
33472
+ var import_node_url = require("node:url");
33473
+ var import_meta = {};
33474
+ function resolveEnvPlistDir() {
33475
+ const envPath = (0, import_node_path6.join)(getResourcesPath(), "plist");
33476
+ if (!(0, import_node_fs3.existsSync)(envPath)) {
33477
+ return null;
33478
+ }
33479
+ const absolutePath = (0, import_node_path6.isAbsolute)(envPath) ? envPath : (0, import_node_path6.join)(process.cwd(), envPath);
33480
+ if ((0, import_node_fs3.existsSync)(absolutePath)) {
33481
+ return absolutePath;
33482
+ }
33483
+ return null;
33484
+ }
33485
+ function getPlistDir(overrideDir) {
33486
+ if (overrideDir) {
33487
+ return (0, import_node_path6.resolve)(overrideDir);
33488
+ }
33489
+ const envPlistDir = resolveEnvPlistDir();
33490
+ if (envPlistDir) {
33491
+ return envPlistDir;
33492
+ }
33493
+ let baseDir;
33494
+ try {
33495
+ if (typeof import_meta !== "undefined" && import_meta.url) {
33496
+ const currentFile = (0, import_node_url.fileURLToPath)(import_meta.url);
33497
+ baseDir = (0, import_node_path6.dirname)(currentFile);
33498
+ } else {
33499
+ baseDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
33500
+ }
33501
+ } catch {
33502
+ baseDir = process.cwd();
33503
+ }
33504
+ const sourcePlistDir = (0, import_node_path6.join)(baseDir, "../plist");
33505
+ if ((0, import_node_fs3.existsSync)(sourcePlistDir)) {
33506
+ return sourcePlistDir;
33507
+ }
33508
+ const distPlistDir = (0, import_node_path6.join)(baseDir, "../plist");
33509
+ if ((0, import_node_fs3.existsSync)(distPlistDir)) {
33510
+ return distPlistDir;
33511
+ }
33512
+ const distPlistDir2 = (0, import_node_path6.join)(baseDir, "../dist/plist");
33513
+ if ((0, import_node_fs3.existsSync)(distPlistDir2)) {
33514
+ return distPlistDir2;
33515
+ }
33516
+ const srcPlistFallback = (0, import_node_path6.join)(process.cwd(), "src/plist");
33517
+ if ((0, import_node_fs3.existsSync)(srcPlistFallback)) {
33518
+ return srcPlistFallback;
33519
+ }
33520
+ const distPlistFallback = (0, import_node_path6.join)(process.cwd(), "dist/plist");
33521
+ if ((0, import_node_fs3.existsSync)(distPlistFallback)) {
33522
+ return distPlistFallback;
33523
+ }
33524
+ return (0, import_node_path6.join)(process.cwd(), "src/plist");
33525
+ }
33526
+ async function loadTemplate(templateName, plistDir) {
33527
+ const templatePath = (0, import_node_path6.join)(getPlistDir(plistDir), templateName);
33528
+ const content = await (0, import_promises.readFile)(templatePath, "utf-8");
33529
+ return content;
33530
+ }
33531
+ function processTemplate(template, variables) {
33532
+ let result = template;
33533
+ result = result.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (_match, key, content) => {
33534
+ const value = variables[key];
33535
+ if (value && value !== false && value !== "" && value !== null && value !== void 0) {
33536
+ return processTemplate(content, variables);
33537
+ }
33538
+ return "";
33539
+ });
33540
+ result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
33541
+ const value = variables[key];
33542
+ if (value === void 0 || value === null) {
33543
+ return "";
33544
+ }
33545
+ if (typeof value === "boolean") {
33546
+ return value ? "true" : "false";
33547
+ }
33548
+ return escapeXml(String(value));
33549
+ });
33550
+ return result;
33551
+ }
33552
+ function escapeXml(str) {
33553
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
33554
+ }
33555
+
33556
+ // src/utils/wifiProfile.ts
33557
+ async function generateWifiProfile(config, options) {
33558
+ const {
33559
+ ssid,
33560
+ password,
33561
+ encryptionType = "WPA2",
33562
+ autoJoin = true,
33563
+ hiddenNetwork = false,
33564
+ organizationName = "MCE Systems",
33565
+ displayName = `WiFi - ${ssid}`,
33566
+ enterprise = false,
33567
+ username,
33568
+ eapType = "PEAP",
33569
+ acceptAnyCertificate = true
33289
33570
  } = config;
33290
33571
  const profileUuid = (0, import_node_crypto2.randomUUID)().toUpperCase();
33291
33572
  const payloadUuid = (0, import_node_crypto2.randomUUID)().toUpperCase();
@@ -33396,252 +33677,23 @@ function parseWifiEapType(value) {
33396
33677
  }
33397
33678
  }
33398
33679
 
33399
- // src/logic/iosCli.ts
33400
- var import_node_child_process2 = require("node:child_process");
33401
- var import_node_path5 = require("node:path");
33402
- function runIosCommand(iosBinaryPath, args) {
33403
- return new Promise((resolve2, reject) => {
33404
- const child = (0, import_node_child_process2.spawn)(iosBinaryPath, args, {
33405
- windowsHide: true,
33406
- stdio: ["ignore", "pipe", "pipe"]
33407
- });
33408
- let stdout = "";
33409
- let stderr = "";
33410
- child.stdout?.on("data", (data) => {
33411
- stdout += data.toString();
33412
- });
33413
- child.stderr?.on("data", (data) => {
33414
- stderr += data.toString();
33415
- });
33416
- child.on("error", (error) => {
33417
- reject(error);
33418
- });
33419
- child.on("close", (code) => {
33420
- console.log(`stdout: ${stdout}`);
33421
- console.log(`stderr: ${stderr}`);
33422
- console.log(`exitCode: ${code}`);
33423
- const output = stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => safeParseJson(line)).filter((item) => Boolean(item));
33424
- const logMessages = stderr.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => safeParseJson(line)).filter((item) => Boolean(item)).map((lineJson) => ({
33425
- level: lineJson.level,
33426
- msg: lineJson.msg,
33427
- timestamp: lineJson.timestamp
33428
- }));
33429
- resolve2({
33430
- command: iosBinaryPath,
33431
- args,
33432
- output,
33433
- logMessages,
33434
- exitCode: code ?? 0
33435
- });
33436
- });
33437
- });
33438
- }
33439
- function safeParseJson(line) {
33440
- try {
33441
- const parsed = JSON.parse(line);
33442
- return parsed && typeof parsed === "object" ? parsed : void 0;
33443
- } catch {
33444
- return void 0;
33445
- }
33446
- }
33447
- function isRecord(value) {
33448
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
33449
- }
33450
- function getStringField(record, key) {
33451
- const value = record[key];
33452
- return typeof value === "string" ? value : void 0;
33453
- }
33454
- function getBooleanField(record, key) {
33455
- const value = record[key];
33456
- return typeof value === "boolean" ? value : void 0;
33457
- }
33458
- function getNumberField(record, key) {
33459
- const value = record[key];
33460
- return typeof value === "number" ? value : void 0;
33461
- }
33462
- function getRecordField(record, key) {
33463
- const value = record[key];
33464
- return isRecord(value) ? value : void 0;
33465
- }
33466
- function toProfileManifest(record) {
33467
- if (!record) {
33468
- return void 0;
33680
+ // src/logic/activationFlow.ts
33681
+ var DEFAULT_RETRIES = 150;
33682
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
33683
+ var ActivationFlow = class {
33684
+ iosCli;
33685
+ mdmClientPromise;
33686
+ constructor(iosCli2) {
33687
+ this.iosCli = iosCli2;
33688
+ this.mdmClientPromise = createMDMClient();
33469
33689
  }
33470
- const description = getStringField(record, "Description");
33471
- const isActive = getBooleanField(record, "IsActive");
33472
- if (description === void 0 && isActive === void 0) {
33473
- return void 0;
33474
- }
33475
- return {
33476
- Description: description,
33477
- IsActive: isActive
33478
- };
33479
- }
33480
- function toProfileMetadata(record) {
33481
- if (!record) {
33482
- return void 0;
33483
- }
33484
- const payloadDescription = getStringField(record, "PayloadDescription");
33485
- const payloadDisplayName = getStringField(record, "PayloadDisplayName");
33486
- const payloadRemovalDisallowed = getBooleanField(record, "PayloadRemovalDisallowed");
33487
- const payloadUuid = getStringField(record, "PayloadUUID");
33488
- const payloadVersion = getNumberField(record, "PayloadVersion");
33489
- if (payloadDescription === void 0 && payloadDisplayName === void 0 && payloadRemovalDisallowed === void 0 && payloadUuid === void 0 && payloadVersion === void 0) {
33490
- return void 0;
33491
- }
33492
- return {
33493
- PayloadDescription: payloadDescription,
33494
- PayloadDisplayName: payloadDisplayName,
33495
- PayloadRemovalDisallowed: payloadRemovalDisallowed,
33496
- PayloadUUID: payloadUuid,
33497
- PayloadVersion: payloadVersion
33498
- };
33499
- }
33500
- function toProfileListEntry(item) {
33501
- const identifier = getStringField(item, "Identifier");
33502
- const manifest = toProfileManifest(getRecordField(item, "Manifest"));
33503
- const metadata = toProfileMetadata(getRecordField(item, "Metadata"));
33504
- const status = getStringField(item, "Status");
33505
- if (!identifier && !manifest && !metadata && !status) {
33506
- return void 0;
33507
- }
33508
- return {
33509
- Identifier: identifier,
33510
- Manifest: manifest,
33511
- Metadata: metadata,
33512
- Status: status
33513
- };
33514
- }
33515
- function parseProfileListOutput(output) {
33516
- const profiles = [];
33517
- for (const item of output) {
33518
- if (Array.isArray(item)) {
33519
- for (const entry2 of item) {
33520
- if (!isRecord(entry2)) {
33521
- continue;
33522
- }
33523
- const profileEntry = toProfileListEntry(entry2);
33524
- if (profileEntry) {
33525
- profiles.push(profileEntry);
33526
- }
33527
- }
33528
- continue;
33529
- }
33530
- if (!isRecord(item)) {
33531
- continue;
33532
- }
33533
- const entry = toProfileListEntry(item);
33534
- if (entry) {
33535
- profiles.push(entry);
33536
- }
33537
- }
33538
- return profiles;
33539
- }
33540
- function createIosCli(iosBinaryPath) {
33541
- return {
33542
- async listDevices() {
33543
- const raw = await runIosCommand(iosBinaryPath, ["list"]);
33544
- const output = raw.output[0];
33545
- return {
33546
- udids: output.deviceList,
33547
- raw
33548
- };
33549
- },
33550
- async wipe(deviceId) {
33551
- return runIosCommand(iosBinaryPath, ["erase", "--force", "--udid", deviceId]);
33552
- },
33553
- async installProfile(deviceId, profilePath) {
33554
- return runIosCommand(iosBinaryPath, ["profile", "add", profilePath, "--udid", deviceId]);
33555
- },
33556
- async removeProfile(deviceId, profileName) {
33557
- return runIosCommand(iosBinaryPath, ["profile", "remove", profileName, "--udid", deviceId]);
33558
- },
33559
- async skipSteps(deviceId) {
33560
- const resourcesDir2 = getResourcesPath();
33561
- return runIosCommand(iosBinaryPath, [
33562
- "prepare",
33563
- "--skip-all",
33564
- `--certfile=${(0, import_node_path5.join)(resourcesDir2, "cert.der")}`,
33565
- `--orgname=${process.env.ORGANIZATION_NAME}`,
33566
- "--udid",
33567
- deviceId
33568
- ]);
33569
- },
33570
- async activate(deviceId) {
33571
- return runIosCommand(iosBinaryPath, ["activate", "--udid", deviceId]);
33572
- },
33573
- async pair(deviceId) {
33574
- return runIosCommand(iosBinaryPath, ["pair", "--udid", deviceId]);
33575
- },
33576
- async forward(deviceId, fromPort, toPort) {
33577
- return runIosCommand(iosBinaryPath, [
33578
- "forward",
33579
- `--port=${fromPort}:${toPort}`,
33580
- "--udid",
33581
- deviceId
33582
- ]);
33583
- },
33584
- async info(deviceId) {
33585
- return runIosCommand(iosBinaryPath, ["info", "--udid", deviceId]);
33586
- },
33587
- async listProfiles(deviceId) {
33588
- const raw = await runIosCommand(iosBinaryPath, ["profile", "list", "--udid", deviceId]);
33589
- return {
33590
- profiles: parseProfileListOutput(raw.output),
33591
- raw
33592
- };
33593
- }
33594
- };
33595
- }
33596
-
33597
- // src/logic/activationFlow.ts
33598
- var DEFAULT_RETRIES = 150;
33599
- var DEFAULT_RETRY_DELAY_MS = 1e3;
33600
- var MCE_MDM_PROFILE_PREFIX = "com.mce.mdm";
33601
- var ActivationFlow = class {
33602
- iosCli;
33603
- mdmClientPromise;
33604
- constructor() {
33605
- const iosBinaryPath = resolveIosBinaryPath();
33606
- if (!iosBinaryPath) {
33607
- throw new Error("iosBinaryPath is required. Provide iosBinaryPath or resourcesDir.");
33608
- }
33609
- this.iosCli = createIosCli(iosBinaryPath);
33610
- this.mdmClientPromise = createMDMClient();
33611
- }
33612
- async run(udid) {
33613
- logTask(`Starting activation flow for device ${udid}`);
33614
- const activationState = await this.getActivationState(udid);
33615
- if (activationState === "Activated") {
33616
- const isEnrolled = await this.isEnrolledToMceMdm(udid);
33617
- if (isEnrolled) {
33618
- logInfo("Device already activated and enrolled to MCE MDM. Skipping activation flow.");
33619
- return async () => {
33620
- };
33621
- }
33622
- } else if (activationState) {
33623
- logInfo(`Activation state is ${activationState}, activating device`);
33624
- await this.retryActivateCommand("activate device", () => this.iosCli.activate(udid));
33625
- } else {
33626
- await this.retryIosCommand("wipe", () => this.iosCli.wipe(udid));
33627
- return void 0;
33628
- }
33629
- const wifiProfileIdentifier = await this.installWifiProfile(udid);
33630
- await this.installMdmProfile(udid);
33631
- await this.retryIosCommand("skip steps", () => this.iosCli.skipSteps(udid));
33632
- return () => this.removeWifiProfile(udid, wifiProfileIdentifier);
33633
- }
33634
- async isEnrolledToMceMdm(udid) {
33635
- const profiles = await this.listProfiles(udid);
33636
- return profiles.some((profile) => isMceMdmProfile(profile));
33637
- }
33638
- async listProfiles(udid) {
33639
- const result = await this.retry(
33640
- "list profiles",
33641
- () => this.iosCli.listProfiles(udid),
33642
- (response) => response.raw.exitCode === 0
33643
- );
33644
- return result.profiles;
33690
+ async run(udid) {
33691
+ logTask(`Starting activation flow for device ${udid}`);
33692
+ await this.retryActivateCommand("activate device", () => this.iosCli.activate(udid));
33693
+ const wifiProfileIdentifier = await this.installWifiProfile(udid);
33694
+ await this.installMdmProfile(udid);
33695
+ await this.retryIosCommand("skip steps", () => this.iosCli.skipSteps(udid));
33696
+ return () => this.removeWifiProfile(udid, wifiProfileIdentifier);
33645
33697
  }
33646
33698
  async installWifiProfile(udid) {
33647
33699
  const wifiProfile = await generateWifiProfileFromEnv();
@@ -33657,14 +33709,6 @@ var ActivationFlow = class {
33657
33709
  await removeTempFile(wifiProfilePath, "wifi profile");
33658
33710
  return wifiProfileIdentifier;
33659
33711
  }
33660
- async getActivationState(udid) {
33661
- const infoResult = await this.retryIosCommand("device info", () => this.iosCli.info(udid));
33662
- const activationState = getActivationState2(infoResult.output);
33663
- if (!activationState) {
33664
- logInfo("Activation state not found");
33665
- }
33666
- return activationState;
33667
- }
33668
33712
  async installMdmProfile(udid) {
33669
33713
  const client = await this.requireMdmClient();
33670
33714
  logTask("Installing MDM enrollment profile");
@@ -33706,7 +33750,7 @@ var ActivationFlow = class {
33706
33750
  async retryActivateCommand(label, command) {
33707
33751
  return this.retry(label, command, (result) => isActivationSuccess(result));
33708
33752
  }
33709
- async retry(label, command, isSuccess, retries = DEFAULT_RETRIES, retryDelayMs = DEFAULT_RETRY_DELAY_MS) {
33753
+ async retry(label, command, isSuccess, retries = +(process.env.DEFAULT_RETRIES ?? DEFAULT_RETRIES), retryDelayMs = +(process.env.DEFAULT_RETRY_DELAY_MS ?? DEFAULT_RETRY_DELAY_MS)) {
33710
33754
  for (let attempt = 0; attempt < retries; attempt += 1) {
33711
33755
  try {
33712
33756
  const result = await command();
@@ -33724,7 +33768,7 @@ var ActivationFlow = class {
33724
33768
  }
33725
33769
  };
33726
33770
  async function saveProfileToTemp(profile, prefix) {
33727
- const tempFilePath = (0, import_node_path6.join)((0, import_node_os2.tmpdir)(), `mce_${prefix}_${Date.now()}.mobileconfig`);
33771
+ const tempFilePath = (0, import_node_path7.join)((0, import_node_os2.tmpdir)(), `mce_${prefix}_${Date.now()}.mobileconfig`);
33728
33772
  await (0, import_promises2.writeFile)(tempFilePath, profile, "utf-8");
33729
33773
  logInfo(`Profile saved to: ${tempFilePath}`);
33730
33774
  return tempFilePath;
@@ -33754,475 +33798,226 @@ function getProfileIdentifierFromProfile(profile) {
33754
33798
  function resolveIosBinaryPath() {
33755
33799
  const platform = process.platform;
33756
33800
  const binaryName = platform === "win32" ? "ios.exe" : "ios";
33757
- return (0, import_node_path6.join)(getResourcesBinPath(), binaryName);
33758
- }
33759
- function getActivationState2(output) {
33760
- if (!Array.isArray(output) || output.length === 0) {
33761
- return void 0;
33762
- }
33763
- for (const item of output) {
33764
- if (!item || typeof item !== "object") {
33765
- continue;
33766
- }
33767
- if ("ActivationState" in item) {
33768
- const value = item.ActivationState;
33769
- return typeof value === "string" ? value : void 0;
33770
- }
33771
- if ("activationState" in item) {
33772
- const value = item.activationState;
33773
- return typeof value === "string" ? value : void 0;
33774
- }
33775
- }
33776
- return void 0;
33801
+ return (0, import_node_path7.join)(getResourcesBinPath(), binaryName);
33777
33802
  }
33778
33803
  function isActivationSuccess(result) {
33779
33804
  if (result.exitCode !== 0) {
33780
33805
  return false;
33781
33806
  }
33782
- if (result.logMessages.length === 0) {
33783
- return true;
33784
- }
33785
- const hasFatal = result.logMessages.some((log) => log.level === "error");
33786
- return !hasFatal;
33787
- }
33788
- function isMceMdmProfile(profile) {
33789
- const identifier = profile.Identifier;
33790
- if (!identifier) {
33791
- return false;
33792
- }
33793
- if (!identifier.startsWith(MCE_MDM_PROFILE_PREFIX)) {
33794
- return false;
33795
- }
33796
- if (profile.Manifest?.IsActive === false) {
33797
- return false;
33798
- }
33799
- return true;
33800
- }
33801
-
33802
- // src/logic/dataParser.ts
33803
- function parsePlistOutput(output) {
33804
- const result = {};
33805
- const lines = output.split("\n");
33806
- for (const line of lines) {
33807
- const colonIndex = line.indexOf(":");
33808
- if (colonIndex > 0) {
33809
- const key = line.substring(0, colonIndex).trim();
33810
- const value = line.substring(colonIndex + 1).trim();
33811
- result[key] = value;
33812
- }
33813
- }
33814
- return result;
33815
- }
33816
- function parseAppList(output) {
33817
- const apps = [];
33818
- const lines = output.trim().split("\n");
33819
- for (const line of lines) {
33820
- const match = line.match(/^([^,]+),\s*([^,]+),\s*"?([^"]+)"?/);
33821
- if (match) {
33822
- apps.push({
33823
- bundleId: match[1].trim(),
33824
- version: match[2].trim(),
33825
- displayName: match[3].trim(),
33826
- bundleVersion: ""
33827
- });
33828
- }
33829
- }
33830
- return apps;
33831
- }
33832
-
33833
- // src/logic/actions/device.ts
33834
- async function getDeviceInfo(udid) {
33835
- logTask(`Getting device info for ${udid}`);
33836
- const result = await runIDeviceTool("ideviceinfo", ["-u", udid]);
33837
- if (!result) {
33838
- throw new Error("Failed to get device info");
33839
- }
33840
- const props = parsePlistOutput(result.stdout);
33841
- return {
33842
- deviceName: props.DeviceName || "",
33843
- productType: props.ProductType || "",
33844
- productVersion: props.ProductVersion || "",
33845
- buildVersion: props.BuildVersion || "",
33846
- serialNumber: props.SerialNumber || "",
33847
- udid: props.UniqueDeviceID || udid,
33848
- wifiAddress: props.WiFiAddress || "",
33849
- bluetoothAddress: props.BluetoothAddress || "",
33850
- phoneNumber: props.PhoneNumber || "",
33851
- cpuArchitecture: props.CPUArchitecture || "",
33852
- hardwareModel: props.HardwareModel || "",
33853
- modelNumber: props.ModelNumber || "",
33854
- regionInfo: props.RegionInfo || "",
33855
- timeZone: props.TimeZone || "",
33856
- uniqueChipID: props.UniqueChipID || "",
33857
- isPaired: true
33858
- // If we can get info, device is paired
33859
- };
33860
- }
33861
- async function info(udid) {
33862
- logTask(`Getting device info for ${udid}`);
33863
- const iosBinaryPath = resolveIosBinaryPath();
33864
- if (!iosBinaryPath) {
33865
- throw new Error("iosBinaryPath is required. Provide iosBinaryPath or resourcesDir.");
33866
- }
33867
- const iosCli = createIosCli(iosBinaryPath);
33868
- const result = await iosCli.info(udid);
33869
- if (!result) {
33870
- throw new Error("Failed to get device info");
33871
- }
33872
- return result.output[0];
33873
- }
33874
-
33875
- // src/logic/actions/pair.ts
33876
- async function isPaired(udid) {
33877
- logTask(`Checking pairing status for ${udid}`);
33878
- try {
33879
- const result = await runIDeviceTool("idevicepair", ["-u", udid, "validate"]);
33880
- if (!result) {
33881
- return false;
33882
- }
33883
- return result.stdout.toLowerCase().includes("success");
33884
- } catch {
33885
- return false;
33886
- }
33887
- }
33888
- async function trustDevice(udid, timeout2 = 6e4, onWaitingForTrust) {
33889
- logTask(`Trusting device ${udid}`);
33890
- if (await isPaired(udid)) {
33891
- logInfo(`Device ${udid} is already trusted`);
33892
- return true;
33893
- }
33894
- logInfo(`Initiating pairing for device ${udid}`);
33895
- try {
33896
- const pairResult = await pair(udid);
33897
- if (pairResult) {
33898
- logInfo(`Device ${udid} paired successfully`);
33899
- return true;
33900
- }
33901
- } catch (error) {
33902
- logError(`Pairing failed with ${udid}: ${error}`);
33903
- }
33904
- logInfo("Please accept the trust dialog on the device...");
33905
- onWaitingForTrust?.();
33906
- try {
33907
- await waitForPairing(udid, timeout2, 1e3);
33908
- logInfo(`Device ${udid} is now trusted`);
33909
- return true;
33910
- } catch {
33911
- logInfo(`Timeout waiting for trust acceptance on device ${udid}`);
33912
- return false;
33913
- }
33914
- }
33915
- async function waitForPairing(udid, timeout2 = 12e4, pollInterval = 1e3) {
33916
- logTask(`Waiting for pairing on device ${udid}`);
33917
- const startTime = Date.now();
33918
- while (Date.now() - startTime < timeout2) {
33919
- if (await isPaired(udid)) {
33920
- logInfo(`Device ${udid} is now paired`);
33921
- return true;
33922
- }
33923
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
33924
- }
33925
- throw new Error(`Timeout waiting for device pairing after ${timeout2}ms`);
33926
- }
33927
- async function pair(udid) {
33928
- logTask(`Initiating pairing for device ${udid}`);
33929
- try {
33930
- const result = await runIDeviceTool("idevicepair", ["-u", udid, "pair"]);
33931
- if (!result) {
33932
- return false;
33933
- }
33934
- return result.stdout.toLowerCase().includes("success");
33935
- } catch (error) {
33936
- const errorMsg = error instanceof Error ? error.message : String(error);
33937
- if (errorMsg.includes("Please accept the trust dialog")) {
33938
- return false;
33939
- }
33940
- throw error;
33941
- }
33942
- }
33943
- async function unpair(udid) {
33944
- logTask(`Un-pairing device ${udid}`);
33945
- try {
33946
- const result = await runIDeviceTool("idevicepair", ["-u", udid, "unpair"]);
33947
- if (!result) {
33948
- return false;
33949
- }
33950
- return result.stdout.toLowerCase().includes("success");
33951
- } catch {
33952
- return false;
33953
- }
33954
- }
33955
-
33956
- // src/logic/actions/install.ts
33957
- async function installLocalApp(ipaPath, udid) {
33958
- logTask(`Installing app ${ipaPath} on device ${udid}`);
33959
- await runIDeviceTool("ideviceinstaller", ["-u", udid, "install", ipaPath]);
33960
- }
33961
- async function installManagedApp(ipaPath, udid, options) {
33962
- await installLocalApp(ipaPath, udid);
33963
- const client = await createMDMClient();
33964
- logTask("Installing app via MDM for management takeover");
33965
- if (client) {
33966
- client.installApp(udid, options);
33967
- }
33968
- }
33969
- async function uninstallApp(bundleId, udid) {
33970
- logTask(`Uninstalling app ${bundleId} from device ${udid}`);
33971
- if (!await isPaired(udid)) {
33972
- await waitForPairing(udid, 1e4);
33973
- }
33974
- await runIDeviceTool("ideviceinstaller", ["-u", udid, "uninstall", bundleId]);
33975
- }
33976
- async function listApps(udid) {
33977
- logTask(`Listing apps on device ${udid}`);
33978
- if (!await isPaired(udid)) {
33979
- await waitForPairing(udid, 1e4);
33980
- }
33981
- try {
33982
- const result = await runIDeviceTool("ideviceinstaller", ["-u", udid, "list"]);
33983
- if (!result) {
33984
- return [];
33985
- }
33986
- const { stdout } = result;
33987
- return parseAppList(stdout);
33988
- } catch {
33989
- return [];
33990
- }
33991
- }
33992
- async function isAppInstalled(bundleId, udid) {
33993
- logTask(`Checking if app ${bundleId} is installed on device ${udid}`);
33994
- const apps = await listApps(udid);
33995
- return apps.some((app) => app.bundleId === bundleId);
33996
- }
33997
- async function wakeDevice(udid) {
33998
- try {
33999
- logInfo("Attempting to wake device screen...");
34000
- await runIDeviceTool("ideviceinfo", ["-u", udid, "-k", "DeviceName"]);
34001
- try {
34002
- await runIDeviceTool("ideviceinfo", ["-u", udid, "-k", "ActivationState"]);
34003
- } catch {
34004
- }
34005
- try {
34006
- await runIDeviceTool("idevicepair", ["-u", udid, "validate"]);
34007
- } catch {
34008
- }
34009
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
34010
- logInfo("Device wake attempt completed");
34011
- } catch (error) {
34012
- logInfo(
34013
- `Device wake attempt failed (non-critical): ${error instanceof Error ? error.message : String(error)}`
34014
- );
34015
- }
34016
- }
34017
- async function launchApp(bundleId, args, udid) {
34018
- logTask(`Launching app ${bundleId} on device ${udid}`);
34019
- if (!await isPaired(udid)) {
34020
- await waitForPairing(udid, 1e4);
34021
- }
34022
- const installed = await isAppInstalled(bundleId, udid);
34023
- if (!installed) {
34024
- throw new Error(
34025
- `App "${bundleId}" is not installed on the device. Install the app first or verify the bundle identifier is correct.`
34026
- );
34027
- }
34028
- await wakeDevice(udid);
34029
- try {
34030
- logInfo(`Attempting to launch ${bundleId} using idevicedebug...`);
34031
- const result = await runIDeviceTool("idevicedebug", ["-u", udid, "run", bundleId, ...args]);
34032
- const output = (result?.stdout ?? "") + (result?.stderr ?? "");
34033
- if (output.trim()) {
34034
- logInfo(`idevicedebug output: ${output.substring(0, 200)}`);
34035
- }
34036
- if (output.toLowerCase().includes("could not start") && output.toLowerCase().includes("debugserver")) {
34037
- logInfo("idevicedebug requires debugserver, falling back to pymobiledevice3...");
34038
- throw new Error("debugserver_not_available");
34039
- }
34040
- logInfo(`App ${bundleId} launched successfully using idevicedebug`);
34041
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
34042
- return;
34043
- } catch (error) {
34044
- const errorMsg = error instanceof Error ? error.message : String(error);
34045
- if (errorMsg.includes("debugserver_not_available") || errorMsg.toLowerCase().includes("could not start") || errorMsg.toLowerCase().includes("debugserver")) {
34046
- logInfo("idevicedebug failed, trying pymobiledevice3 for iOS 17+...");
34047
- await launchAppWithPymobiledevice3(bundleId, args, udid);
34048
- return;
34049
- }
34050
- throw error;
34051
- }
34052
- }
34053
- async function launchAppWithPymobiledevice3(bundleId, args, udid) {
34054
- logTask(`Launching app ${bundleId} using pymobiledevice3`);
34055
- const { exec } = await import("node:child_process");
34056
- const { promisify: promisify3 } = await import("node:util");
34057
- const execAsync2 = promisify3(exec);
34058
- try {
34059
- let cmdArgs = [
34060
- "-m",
34061
- "pymobiledevice3",
34062
- "developer",
34063
- "dvt",
34064
- "launch",
34065
- "--udid",
34066
- udid,
34067
- "--kill-existing",
34068
- // Kill existing instance to bring app to foreground
34069
- bundleId,
34070
- ...args
34071
- ];
34072
- let command = `python ${cmdArgs.map((a) => `"${a}"`).join(" ")}`;
34073
- logInfo(`Executing: ${command}`);
34074
- const result = await execAsync2(command, {
34075
- windowsHide: true,
34076
- encoding: "utf8",
34077
- timeout: 3e4
34078
- // 30 second timeout
34079
- });
34080
- const output = result.stdout.toString() + result.stderr.toString();
34081
- if (output.trim()) {
34082
- logInfo(`pymobiledevice3 output: ${output.substring(0, 200)}`);
34083
- }
34084
- if (output.toLowerCase().includes("developer mode") && output.toLowerCase().includes("not enabled")) {
34085
- throw new Error(
34086
- "Developer Mode is not enabled on the device.\nPlease enable it: Settings \u2192 Privacy & Security \u2192 Developer Mode \u2192 Enable"
34087
- );
34088
- }
34089
- if (output.toLowerCase().includes("tunneld") && (output.toLowerCase().includes("unable to connect") || output.toLowerCase().includes("invalidserviceerror"))) {
34090
- logInfo("Tunnel required, retrying with tunnel option...");
34091
- cmdArgs = [
34092
- "-m",
34093
- "pymobiledevice3",
34094
- "developer",
34095
- "dvt",
34096
- "launch",
34097
- "--udid",
34098
- udid,
34099
- "--tunnel",
34100
- udid,
34101
- // Use UDID for tunnel
34102
- "--kill-existing",
34103
- bundleId,
34104
- ...args
34105
- ];
34106
- command = `python ${cmdArgs.map((a) => `"${a}"`).join(" ")}`;
34107
- logInfo(`Retrying with tunnel: ${command}`);
34108
- try {
34109
- const retryResult = await execAsync2(command, {
34110
- windowsHide: true,
34111
- encoding: "utf8",
34112
- timeout: 3e4
34113
- });
34114
- const retryOutput = retryResult.stdout.toString() + retryResult.stderr.toString();
34115
- if (retryOutput.trim()) {
34116
- logInfo(`pymobiledevice3 retry output: ${retryOutput.substring(0, 200)}`);
34117
- }
34118
- if (retryOutput.toLowerCase().includes("tunneld") && retryOutput.toLowerCase().includes("unable to connect")) {
34119
- throw new Error(
34120
- "Tunnel connection failed. For iOS 17+, you may need to start tunneld:\n python -m pymobiledevice3 remote tunneld\nOr ensure Developer Mode is enabled and device is unlocked."
34121
- );
34122
- }
34123
- logInfo(`App ${bundleId} launched successfully using pymobiledevice3 with tunnel`);
34124
- await new Promise((resolve2) => setTimeout(resolve2, 500));
34125
- return;
34126
- } catch {
34127
- throw new Error(
34128
- "Tunnel connection failed. For iOS 17+, you may need to start tunneld:\n python -m pymobiledevice3 remote tunneld\nOr ensure Developer Mode is enabled and device is unlocked."
34129
- );
34130
- }
34131
- }
34132
- if (output.toLowerCase().includes("not found") || output.toLowerCase().includes("command not found") || output.toLowerCase().includes("cannot find")) {
34133
- throw new Error(
34134
- "pymobiledevice3 is not installed.\nInstall it with: python -m pip install --user pymobiledevice3"
34135
- );
34136
- }
34137
- if (output.toLowerCase().includes("error") && !output.toLowerCase().includes("warning")) {
34138
- logInfo(`Warning: pymobiledevice3 reported errors: ${output.substring(0, 300)}`);
34139
- }
34140
- logInfo(`App ${bundleId} launched successfully using pymobiledevice3`);
34141
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
34142
- } catch (error) {
34143
- const errorMsg = error instanceof Error ? error.message : String(error);
34144
- if (errorMsg.includes("not found") || errorMsg.includes("command not found") || errorMsg.includes("cannot find") || errorMsg.includes("No module named")) {
34145
- throw new Error(
34146
- "pymobiledevice3 is not installed or Python is not available.\nInstall it with: python -m pip install --user pymobiledevice3\nFor iOS 17+, Developer Mode must also be enabled on the device."
34147
- );
34148
- }
34149
- throw error;
33807
+ if (result.logMessages.length === 0) {
33808
+ return true;
34150
33809
  }
33810
+ const hasFatal = result.logMessages.some((log) => log.level === "error");
33811
+ return !hasFatal;
34151
33812
  }
34152
33813
 
34153
- // src/logic/actions/proxy.ts
33814
+ // src/logic/iosCli.ts
34154
33815
  var import_node_child_process3 = require("node:child_process");
34155
33816
  var import_node_path8 = require("node:path");
34156
- function startPortForward(localPort, devicePort, udid, startupTimeout = 500) {
33817
+ var ios;
33818
+ function iosCli() {
33819
+ const iosBinaryPath = resolveIosBinaryPath();
33820
+ if (!iosBinaryPath) {
33821
+ throw new Error("iosBinaryPath is required. Provide iosBinaryPath or resourcesDir.");
33822
+ }
33823
+ return createIosCli(iosBinaryPath);
33824
+ }
33825
+ if (!ios) {
33826
+ ios = iosCli();
33827
+ }
33828
+ function runIosCommand(iosBinaryPath, args) {
34157
33829
  return new Promise((resolve2, reject) => {
34158
- logTask(`Starting port forward ${localPort} -> ${devicePort} for device ${udid}`);
34159
- const binPath = getResourcesBinPath();
34160
- const ext = process.platform === "win32" ? ".exe" : "";
34161
- const toolPath = binPath ? (0, import_node_path8.join)(binPath, `iproxy${ext}`) : `iproxy${ext}`;
34162
- const spawnOptions = {
33830
+ const child = (0, import_node_child_process3.spawn)(iosBinaryPath, args, {
34163
33831
  windowsHide: true,
34164
33832
  stdio: ["ignore", "pipe", "pipe"]
34165
- };
34166
- if (binPath) {
34167
- spawnOptions.cwd = binPath;
34168
- }
34169
- logInfo(`Spawning iproxy with path: ${toolPath}`);
34170
- logInfo(`Arguments: ${[localPort.toString(), devicePort.toString(), "-u", udid].join(" ")}`);
34171
- logInfo(`Options: ${JSON.stringify(spawnOptions)}`);
34172
- const child = (0, import_node_child_process3.spawn)(
34173
- toolPath,
34174
- [localPort.toString(), devicePort.toString(), "-u", udid],
34175
- spawnOptions
34176
- );
34177
- let hasResolved = false;
34178
- let stderrOutput = "";
33833
+ });
33834
+ let stdout = "";
33835
+ let stderr = "";
34179
33836
  child.stdout?.on("data", (data) => {
34180
- logTask(`${data.toString()}`);
33837
+ stdout += data.toString();
34181
33838
  });
34182
33839
  child.stderr?.on("data", (data) => {
34183
- logError(`${data.toString()}`);
34184
- const msg = data.toString();
34185
- stderrOutput += msg;
34186
- if (msg.toLowerCase().includes("error") && !hasResolved) {
34187
- hasResolved = true;
34188
- child.kill();
34189
- reject(new Error(`Port forwarding failed: ${msg}`));
34190
- }
33840
+ stderr += data.toString();
34191
33841
  });
34192
33842
  child.on("error", (error) => {
34193
- if (!hasResolved) {
34194
- hasResolved = true;
34195
- reject(new Error(`Failed to start iproxy: ${error.message}`));
34196
- }
33843
+ reject(error);
34197
33844
  });
34198
- child.on("exit", (code) => {
34199
- if (!hasResolved) {
34200
- hasResolved = true;
34201
- if (code !== 0 && code !== null) {
34202
- reject(new Error(`iproxy exited with code ${code}: ${stderrOutput}`));
34203
- }
34204
- }
33845
+ child.on("close", (code) => {
33846
+ console.log(`stdout: ${stdout}`);
33847
+ console.log(`stderr: ${stderr}`);
33848
+ console.log(`exitCode: ${code}`);
33849
+ const output = stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => safeParseJson(line)).filter((item) => Boolean(item));
33850
+ const logMessages = stderr.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => safeParseJson(line)).filter((item) => Boolean(item)).map((lineJson) => ({
33851
+ level: lineJson.level,
33852
+ msg: lineJson.msg,
33853
+ timestamp: lineJson.timestamp
33854
+ }));
33855
+ resolve2({
33856
+ command: iosBinaryPath,
33857
+ args,
33858
+ output,
33859
+ logMessages,
33860
+ exitCode: code ?? 0
33861
+ });
34205
33862
  });
34206
- setTimeout(() => {
34207
- if (!hasResolved) {
34208
- hasResolved = true;
34209
- logTask(`Port forward started: ${localPort} -> ${devicePort} for device ${udid}`);
34210
- resolve2({
34211
- result: {
34212
- localPort,
34213
- devicePort
34214
- },
34215
- process: child
34216
- });
34217
- }
34218
- }, startupTimeout);
34219
33863
  });
34220
33864
  }
34221
- function killPortForwardProcess(process2) {
34222
- if (process2 && !process2.killed) {
34223
- logTask("Killing port forward process");
34224
- process2.kill();
33865
+ function safeParseJson(line) {
33866
+ try {
33867
+ const parsed = JSON.parse(line);
33868
+ return parsed && typeof parsed === "object" ? parsed : void 0;
33869
+ } catch {
33870
+ return void 0;
33871
+ }
33872
+ }
33873
+ function isRecord(value) {
33874
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
33875
+ }
33876
+ function getStringField(record, key) {
33877
+ const value = record[key];
33878
+ return typeof value === "string" ? value : void 0;
33879
+ }
33880
+ function getBooleanField(record, key) {
33881
+ const value = record[key];
33882
+ return typeof value === "boolean" ? value : void 0;
33883
+ }
33884
+ function getNumberField(record, key) {
33885
+ const value = record[key];
33886
+ return typeof value === "number" ? value : void 0;
33887
+ }
33888
+ function getRecordField(record, key) {
33889
+ const value = record[key];
33890
+ return isRecord(value) ? value : void 0;
33891
+ }
33892
+ function toProfileManifest(record) {
33893
+ if (!record) {
33894
+ return void 0;
33895
+ }
33896
+ const description = getStringField(record, "Description");
33897
+ const isActive = getBooleanField(record, "IsActive");
33898
+ if (description === void 0 && isActive === void 0) {
33899
+ return void 0;
33900
+ }
33901
+ return {
33902
+ Description: description,
33903
+ IsActive: isActive
33904
+ };
33905
+ }
33906
+ function toProfileMetadata(record) {
33907
+ if (!record) {
33908
+ return void 0;
33909
+ }
33910
+ const payloadDescription = getStringField(record, "PayloadDescription");
33911
+ const payloadDisplayName = getStringField(record, "PayloadDisplayName");
33912
+ const payloadRemovalDisallowed = getBooleanField(record, "PayloadRemovalDisallowed");
33913
+ const payloadUuid = getStringField(record, "PayloadUUID");
33914
+ const payloadVersion = getNumberField(record, "PayloadVersion");
33915
+ if (payloadDescription === void 0 && payloadDisplayName === void 0 && payloadRemovalDisallowed === void 0 && payloadUuid === void 0 && payloadVersion === void 0) {
33916
+ return void 0;
33917
+ }
33918
+ return {
33919
+ PayloadDescription: payloadDescription,
33920
+ PayloadDisplayName: payloadDisplayName,
33921
+ PayloadRemovalDisallowed: payloadRemovalDisallowed,
33922
+ PayloadUUID: payloadUuid,
33923
+ PayloadVersion: payloadVersion
33924
+ };
33925
+ }
33926
+ function toProfileListEntry(item) {
33927
+ const identifier = getStringField(item, "Identifier");
33928
+ const manifest = toProfileManifest(getRecordField(item, "Manifest"));
33929
+ const metadata = toProfileMetadata(getRecordField(item, "Metadata"));
33930
+ const status = getStringField(item, "Status");
33931
+ if (!identifier && !manifest && !metadata && !status) {
33932
+ return void 0;
33933
+ }
33934
+ return {
33935
+ Identifier: identifier,
33936
+ Manifest: manifest,
33937
+ Metadata: metadata,
33938
+ Status: status
33939
+ };
33940
+ }
33941
+ function parseProfileListOutput(output) {
33942
+ const profiles = [];
33943
+ for (const item of output) {
33944
+ if (Array.isArray(item)) {
33945
+ for (const entry2 of item) {
33946
+ if (!isRecord(entry2)) {
33947
+ continue;
33948
+ }
33949
+ const profileEntry = toProfileListEntry(entry2);
33950
+ if (profileEntry) {
33951
+ profiles.push(profileEntry);
33952
+ }
33953
+ }
33954
+ continue;
33955
+ }
33956
+ if (!isRecord(item)) {
33957
+ continue;
33958
+ }
33959
+ const entry = toProfileListEntry(item);
33960
+ if (entry) {
33961
+ profiles.push(entry);
33962
+ }
34225
33963
  }
33964
+ return profiles;
33965
+ }
33966
+ function createIosCli(iosBinaryPath) {
33967
+ return {
33968
+ async listDevices() {
33969
+ const raw = await runIosCommand(iosBinaryPath, ["list"]);
33970
+ const output = raw.output[0];
33971
+ return {
33972
+ udids: output.deviceList,
33973
+ raw
33974
+ };
33975
+ },
33976
+ async wipe(deviceId) {
33977
+ return runIosCommand(iosBinaryPath, ["erase", "--force", "--udid", deviceId]);
33978
+ },
33979
+ async installProfile(deviceId, profilePath) {
33980
+ return runIosCommand(iosBinaryPath, ["profile", "add", profilePath, "--udid", deviceId]);
33981
+ },
33982
+ async removeProfile(deviceId, profileName) {
33983
+ return runIosCommand(iosBinaryPath, ["profile", "remove", profileName, "--udid", deviceId]);
33984
+ },
33985
+ async skipSteps(deviceId) {
33986
+ const resourcesDir2 = getResourcesPath();
33987
+ return runIosCommand(iosBinaryPath, [
33988
+ "prepare",
33989
+ "--skip-all",
33990
+ `--certfile=${(0, import_node_path8.join)(resourcesDir2, "cert.der")}`,
33991
+ `--orgname=${process.env.ORGANIZATION_NAME}`,
33992
+ "--udid",
33993
+ deviceId
33994
+ ]);
33995
+ },
33996
+ async activate(deviceId) {
33997
+ return runIosCommand(iosBinaryPath, ["activate", "--udid", deviceId]);
33998
+ },
33999
+ async pair(deviceId) {
34000
+ return runIosCommand(iosBinaryPath, ["pair", "--udid", deviceId]);
34001
+ },
34002
+ async forward(deviceId, fromPort, toPort) {
34003
+ return runIosCommand(iosBinaryPath, [
34004
+ "forward",
34005
+ `--port=${fromPort}:${toPort}`,
34006
+ "--udid",
34007
+ deviceId
34008
+ ]);
34009
+ },
34010
+ async info(deviceId) {
34011
+ return runIosCommand(iosBinaryPath, ["info", "--udid", deviceId]);
34012
+ },
34013
+ async listProfiles(deviceId) {
34014
+ const raw = await runIosCommand(iosBinaryPath, ["profile", "list", "--udid", deviceId]);
34015
+ return {
34016
+ profiles: parseProfileListOutput(raw.output),
34017
+ raw
34018
+ };
34019
+ }
34020
+ };
34226
34021
  }
34227
34022
 
34228
34023
  // src/logic/appleDeviceKit.ts
@@ -34233,11 +34028,17 @@ var AppleDeviceKit = class {
34233
34028
  logInfo(
34234
34029
  `AppleDeviceKit initialized for device: ${this.deviceId}, logical port: ${this.logicalPort}`
34235
34030
  );
34031
+ const iosBinaryPath = resolveIosBinaryPath();
34032
+ if (!iosBinaryPath) {
34033
+ throw new Error("iosBinaryPath is required. Provide iosBinaryPath or resourcesDir.");
34034
+ }
34035
+ this.iosCli = createIosCli(iosBinaryPath);
34236
34036
  }
34237
34037
  deviceId;
34238
34038
  proxyProcess = null;
34239
34039
  localDevicePort = null;
34240
34040
  isDisposed = false;
34041
+ iosCli;
34241
34042
  static setResourcesDir(dir) {
34242
34043
  setResourcesDir(dir);
34243
34044
  }
@@ -34258,7 +34059,7 @@ var AppleDeviceKit = class {
34258
34059
  }
34259
34060
  async info() {
34260
34061
  this.ensureNotDisposed();
34261
- return info(this.deviceId);
34062
+ return info(this.deviceId, this.iosCli);
34262
34063
  }
34263
34064
  /**
34264
34065
  * Check if device is paired/trusted with this computer
@@ -34406,6 +34207,18 @@ var AppleDeviceKit = class {
34406
34207
  this.ensureNotDisposed();
34407
34208
  return getActivationState(this.deviceId);
34408
34209
  }
34210
+ /**
34211
+ * Wipe the device
34212
+ */
34213
+ async wipe() {
34214
+ return wipeDevice(this.deviceId, this.iosCli);
34215
+ }
34216
+ async removeProfile(profileIdentifier) {
34217
+ return removeProfile(profileIdentifier, this.deviceId, this.iosCli);
34218
+ }
34219
+ async listProfiles() {
34220
+ return listProfiles(this.deviceId, this.iosCli);
34221
+ }
34409
34222
  /**
34410
34223
  * Activate the device using the activation flow.
34411
34224
  *
@@ -34417,7 +34230,7 @@ var AppleDeviceKit = class {
34417
34230
  */
34418
34231
  async activate() {
34419
34232
  this.ensureNotDisposed();
34420
- const flow = new ActivationFlow();
34233
+ const flow = new ActivationFlow(this.iosCli);
34421
34234
  return await flow.run(this.deviceId);
34422
34235
  }
34423
34236
  /**