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