@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 +612 -799
- package/dist/index.js.map +4 -4
- package/dist/index.mjs +613 -800
- package/dist/index.mjs.map +4 -4
- package/dist/types/logic/actions/device.d.ts +5 -1
- package/dist/types/logic/actions/device.d.ts.map +1 -1
- package/dist/types/logic/actions/install.d.ts +1 -2
- package/dist/types/logic/actions/install.d.ts.map +1 -1
- package/dist/types/logic/activationFlow.d.ts +2 -4
- package/dist/types/logic/activationFlow.d.ts.map +1 -1
- package/dist/types/logic/appleDeviceKit.d.ts +8 -0
- package/dist/types/logic/appleDeviceKit.d.ts.map +1 -1
- package/dist/types/logic/iosCli.d.ts +3 -0
- package/dist/types/logic/iosCli.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
32384
|
-
import { join as
|
|
32383
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
32384
|
+
import { join as join3 } from "node:path";
|
|
32385
32385
|
|
|
32386
|
-
// src/logic/
|
|
32387
|
-
|
|
32388
|
-
|
|
32389
|
-
|
|
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
|
|
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 =
|
|
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/
|
|
33176
|
-
|
|
33177
|
-
|
|
33178
|
-
|
|
33179
|
-
|
|
33180
|
-
|
|
33181
|
-
|
|
33182
|
-
|
|
33183
|
-
|
|
33184
|
-
|
|
33185
|
-
|
|
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
|
|
33195
|
-
|
|
33196
|
-
|
|
33197
|
-
|
|
33198
|
-
|
|
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
|
-
|
|
33269
|
+
logInfo(`Initiating pairing for device ${udid}`);
|
|
33203
33270
|
try {
|
|
33204
|
-
|
|
33205
|
-
|
|
33206
|
-
|
|
33207
|
-
|
|
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
|
-
|
|
33276
|
+
} catch (error) {
|
|
33277
|
+
logError(`Pairing failed with ${udid}: ${error}`);
|
|
33212
33278
|
}
|
|
33213
|
-
|
|
33214
|
-
|
|
33215
|
-
|
|
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
|
-
|
|
33218
|
-
|
|
33219
|
-
|
|
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
|
-
|
|
33222
|
-
|
|
33223
|
-
|
|
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
|
-
|
|
33226
|
-
|
|
33227
|
-
|
|
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
|
-
|
|
33230
|
-
|
|
33231
|
-
|
|
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
|
|
33236
|
-
|
|
33237
|
-
|
|
33238
|
-
|
|
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
|
|
33241
|
-
|
|
33242
|
-
|
|
33243
|
-
|
|
33244
|
-
|
|
33245
|
-
|
|
33246
|
-
|
|
33247
|
-
|
|
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
|
-
|
|
33258
|
-
|
|
33259
|
-
|
|
33361
|
+
const { stdout } = result;
|
|
33362
|
+
return parseAppList(stdout);
|
|
33363
|
+
} catch {
|
|
33364
|
+
return [];
|
|
33365
|
+
}
|
|
33260
33366
|
}
|
|
33261
|
-
function
|
|
33262
|
-
|
|
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/
|
|
33266
|
-
|
|
33267
|
-
|
|
33268
|
-
|
|
33269
|
-
|
|
33270
|
-
|
|
33271
|
-
|
|
33272
|
-
|
|
33273
|
-
|
|
33274
|
-
|
|
33275
|
-
|
|
33276
|
-
|
|
33277
|
-
|
|
33278
|
-
|
|
33279
|
-
|
|
33280
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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/
|
|
33390
|
-
|
|
33391
|
-
|
|
33392
|
-
|
|
33393
|
-
|
|
33394
|
-
|
|
33395
|
-
|
|
33396
|
-
|
|
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
|
-
|
|
33461
|
-
|
|
33462
|
-
|
|
33463
|
-
|
|
33464
|
-
|
|
33465
|
-
|
|
33466
|
-
|
|
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 =
|
|
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
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34157
|
-
|
|
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
|
-
|
|
33827
|
+
stdout += data.toString();
|
|
34171
33828
|
});
|
|
34172
33829
|
child.stderr?.on("data", (data) => {
|
|
34173
|
-
|
|
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
|
-
|
|
34184
|
-
hasResolved = true;
|
|
34185
|
-
reject(new Error(`Failed to start iproxy: ${error.message}`));
|
|
34186
|
-
}
|
|
33833
|
+
reject(error);
|
|
34187
33834
|
});
|
|
34188
|
-
child.on("
|
|
34189
|
-
|
|
34190
|
-
|
|
34191
|
-
|
|
34192
|
-
|
|
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
|
|
34212
|
-
|
|
34213
|
-
|
|
34214
|
-
|
|
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
|
/**
|