@mcesystems/apple-kit 1.0.0 → 1.0.2
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 +470 -404
- package/dist/index.js.map +4 -4
- package/dist/types/index.d.ts +6 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/logic/actions/activation.d.ts +12 -0
- package/dist/types/logic/actions/activation.d.ts.map +1 -0
- package/dist/types/logic/actions/device.d.ts +27 -0
- package/dist/types/logic/actions/device.d.ts.map +1 -0
- package/dist/types/logic/actions/install.d.ts +9 -0
- package/dist/types/logic/actions/install.d.ts.map +1 -0
- package/dist/types/logic/actions/pair.d.ts +6 -0
- package/dist/types/logic/actions/pair.d.ts.map +1 -0
- package/dist/types/logic/actions/proxy.d.ts +3 -0
- package/dist/types/logic/actions/proxy.d.ts.map +1 -0
- package/dist/types/logic/appleDeviceKit.d.ts +53 -7
- package/dist/types/logic/appleDeviceKit.d.ts.map +1 -1
- package/dist/types/logic/dataParser.d.ts +23 -0
- package/dist/types/logic/dataParser.d.ts.map +1 -0
- package/dist/types/logic/devicesMonitor.d.ts.map +1 -1
- package/dist/types/logic/enrollment.d.ts +56 -0
- package/dist/types/logic/enrollment.d.ts.map +1 -0
- package/dist/types/logic/skipSetup.d.ts +81 -0
- package/dist/types/logic/skipSetup.d.ts.map +1 -0
- package/dist/types/types/skipSteps.d.ts +136 -0
- package/dist/types/types/skipSteps.d.ts.map +1 -0
- package/dist/types/types.d.ts +22 -5
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/debug.d.ts +8 -0
- package/dist/types/utils/debug.d.ts.map +1 -1
- package/dist/types/utils/idevicePath.d.ts +3 -0
- package/dist/types/utils/idevicePath.d.ts.map +1 -1
- package/dist/types/utils/usbmuxd.d.ts +19 -0
- package/dist/types/utils/usbmuxd.d.ts.map +1 -0
- package/dist/types/utils/wifiProfile.d.ts +72 -0
- package/dist/types/utils/wifiProfile.d.ts.map +1 -0
- package/package.json +4 -3
- package/resources/bin/windows/ideviceactivation.exe +0 -0
- package/resources/bin/windows/libideviceactivation-1.0.dll +0 -0
- package/resources/bin/windows/libimobiledevice-1.0.dll +0 -0
- package/resources/bin/windows/libimobiledevice-glue-1.0.dll +0 -0
- package/resources/bin/windows/libplist-2.0.dll +0 -0
- package/resources/bin/windows/libusbmuxd-2.0.dll +0 -0
- package/resources/lib/include/libimobiledevice/afc.h +413 -0
- package/resources/lib/include/libimobiledevice/bt_packet_logger.h +156 -0
- package/resources/lib/include/libimobiledevice/companion_proxy.h +212 -0
- package/resources/lib/include/libimobiledevice/debugserver.h +272 -0
- package/resources/lib/include/libimobiledevice/diagnostics_relay.h +228 -0
- package/resources/lib/include/libimobiledevice/file_relay.h +166 -0
- package/resources/lib/include/libimobiledevice/heartbeat.h +137 -0
- package/resources/lib/include/libimobiledevice/house_arrest.h +180 -0
- package/resources/lib/include/libimobiledevice/installation_proxy.h +505 -0
- package/resources/lib/include/libimobiledevice/libimobiledevice.h +444 -0
- package/resources/lib/include/libimobiledevice/lockdown.h +577 -0
- package/resources/lib/include/libimobiledevice/misagent.h +168 -0
- package/resources/lib/include/libimobiledevice/mobile_image_mounter.h +275 -0
- package/resources/lib/include/libimobiledevice/mobileactivation.h +192 -0
- package/resources/lib/include/libimobiledevice/mobilebackup.h +246 -0
- package/resources/lib/include/libimobiledevice/mobilebackup2.h +214 -0
- package/resources/lib/include/libimobiledevice/mobilesync.h +359 -0
- package/resources/lib/include/libimobiledevice/notification_proxy.h +202 -0
- package/resources/lib/include/libimobiledevice/ostrace.h +198 -0
- package/resources/lib/include/libimobiledevice/preboard.h +187 -0
- package/resources/lib/include/libimobiledevice/property_list_service.h +184 -0
- package/resources/lib/include/libimobiledevice/restore.h +179 -0
- package/resources/lib/include/libimobiledevice/reverse_proxy.h +213 -0
- package/resources/lib/include/libimobiledevice/sbservices.h +175 -0
- package/resources/lib/include/libimobiledevice/screenshotr.h +118 -0
- package/resources/lib/include/libimobiledevice/service.h +202 -0
- package/resources/lib/include/libimobiledevice/syslog_relay.h +184 -0
- package/resources/lib/include/libimobiledevice/webinspector.h +137 -0
- package/resources/lib/include/plist/Array.h +80 -0
- package/resources/lib/include/plist/Boolean.h +48 -0
- package/resources/lib/include/plist/Data.h +50 -0
- package/resources/lib/include/plist/Date.h +54 -0
- package/resources/lib/include/plist/Dictionary.h +73 -0
- package/resources/lib/include/plist/Integer.h +54 -0
- package/resources/lib/include/plist/Key.h +49 -0
- package/resources/lib/include/plist/Node.h +57 -0
- package/resources/lib/include/plist/Real.h +48 -0
- package/resources/lib/include/plist/String.h +52 -0
- package/resources/lib/include/plist/Structure.h +62 -0
- package/resources/lib/include/plist/Uid.h +48 -0
- package/resources/lib/include/plist/plist++.h +39 -0
- package/resources/lib/include/plist/plist.h +1448 -0
- package/resources/lib/libimobiledevice-1.0.dll +0 -0
- package/resources/lib/libimobiledevice-1.0.dll.a +0 -0
- package/resources/lib/libplist-2.0.dll +0 -0
- package/resources/lib/libplist-2.0.dll.a +0 -0
package/dist/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
// src/logic/appleDeviceKit.ts
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
|
|
4
1
|
// src/utils/debug.ts
|
|
5
2
|
import createDebug from "debug";
|
|
6
3
|
var debug = createDebug("apple-kit");
|
|
7
4
|
var debugTask = createDebug("apple-kit:task");
|
|
5
|
+
var debugWarning = createDebug("apple-kit:warning");
|
|
6
|
+
var debugError = createDebug("apple-kit:error");
|
|
8
7
|
function logInfo(message) {
|
|
9
8
|
debug(message);
|
|
10
9
|
}
|
|
@@ -12,73 +11,13 @@ function logTask(message) {
|
|
|
12
11
|
debugTask(message);
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
// src/
|
|
14
|
+
// src/logic/actions/device.ts
|
|
16
15
|
import { exec as execCallback } from "node:child_process";
|
|
17
|
-
import { promisify } from "node:util";
|
|
18
|
-
|
|
19
|
-
// src/utils/idevicePath.ts
|
|
20
16
|
import { existsSync } from "node:fs";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
24
|
-
var __dirname = dirname(__filename);
|
|
25
|
-
function getResourcesBinPath() {
|
|
26
|
-
const packageRoot = dirname(dirname(__dirname));
|
|
27
|
-
const platform = process.platform;
|
|
28
|
-
let platformDir;
|
|
29
|
-
switch (platform) {
|
|
30
|
-
case "win32":
|
|
31
|
-
platformDir = "windows";
|
|
32
|
-
break;
|
|
33
|
-
case "darwin":
|
|
34
|
-
platformDir = "darwin";
|
|
35
|
-
break;
|
|
36
|
-
case "linux":
|
|
37
|
-
platformDir = "linux";
|
|
38
|
-
break;
|
|
39
|
-
default:
|
|
40
|
-
throw new Error(`Unsupported platform: ${platform}`);
|
|
41
|
-
}
|
|
42
|
-
return join(packageRoot, "resources", "bin", platformDir);
|
|
43
|
-
}
|
|
44
|
-
function getIDeviceToolPath(toolName) {
|
|
45
|
-
const binPath = getResourcesBinPath();
|
|
46
|
-
const ext = process.platform === "win32" ? ".exe" : "";
|
|
47
|
-
const toolPath = join(binPath, `${toolName}${ext}`);
|
|
48
|
-
if (existsSync(toolPath)) {
|
|
49
|
-
return toolPath;
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
function getIDeviceBinPath() {
|
|
54
|
-
return getResourcesBinPath();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// src/utils/exec.ts
|
|
58
|
-
var execAsync = promisify(execCallback);
|
|
59
|
-
async function execIDevice(command, options = {}) {
|
|
60
|
-
const binPath = getIDeviceBinPath();
|
|
61
|
-
const env = {
|
|
62
|
-
...process.env,
|
|
63
|
-
PATH: `${binPath};${process.env.PATH}`
|
|
64
|
-
};
|
|
65
|
-
const result = await execAsync(command, {
|
|
66
|
-
...options,
|
|
67
|
-
env,
|
|
68
|
-
windowsHide: true,
|
|
69
|
-
encoding: "utf8"
|
|
70
|
-
});
|
|
71
|
-
return {
|
|
72
|
-
stdout: result.stdout.toString(),
|
|
73
|
-
stderr: result.stderr.toString()
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
async function runIDeviceTool(toolPath, args = [], options = {}) {
|
|
77
|
-
const command = `"${toolPath}" ${args.map((a) => `"${a}"`).join(" ")}`;
|
|
78
|
-
return execIDevice(command, options);
|
|
79
|
-
}
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { promisify } from "node:util";
|
|
80
19
|
|
|
81
|
-
// src/logic/
|
|
20
|
+
// src/logic/dataParser.ts
|
|
82
21
|
function parsePlistOutput(output) {
|
|
83
22
|
const result = {};
|
|
84
23
|
const lines = output.split("\n");
|
|
@@ -125,6 +64,418 @@ function parseAppList(output) {
|
|
|
125
64
|
}
|
|
126
65
|
return apps;
|
|
127
66
|
}
|
|
67
|
+
|
|
68
|
+
// src/logic/actions/device.ts
|
|
69
|
+
var execAsync = promisify(execCallback);
|
|
70
|
+
async function getDeviceInfo(udid) {
|
|
71
|
+
logTask(`Getting device info for ${udid}`);
|
|
72
|
+
const result = await runIDeviceTool("ideviceinfo", ["-u", udid]);
|
|
73
|
+
if (!result) {
|
|
74
|
+
throw new Error("Failed to get device info");
|
|
75
|
+
}
|
|
76
|
+
const props = parsePlistOutput(result.stdout);
|
|
77
|
+
return {
|
|
78
|
+
deviceName: props.DeviceName || "",
|
|
79
|
+
productType: props.ProductType || "",
|
|
80
|
+
productVersion: props.ProductVersion || "",
|
|
81
|
+
buildVersion: props.BuildVersion || "",
|
|
82
|
+
serialNumber: props.SerialNumber || "",
|
|
83
|
+
udid: props.UniqueDeviceID || udid,
|
|
84
|
+
wifiAddress: props.WiFiAddress || "",
|
|
85
|
+
bluetoothAddress: props.BluetoothAddress || "",
|
|
86
|
+
phoneNumber: props.PhoneNumber || "",
|
|
87
|
+
cpuArchitecture: props.CPUArchitecture || "",
|
|
88
|
+
hardwareModel: props.HardwareModel || "",
|
|
89
|
+
modelNumber: props.ModelNumber || "",
|
|
90
|
+
regionInfo: props.RegionInfo || "",
|
|
91
|
+
timeZone: props.TimeZone || "",
|
|
92
|
+
uniqueChipID: props.UniqueChipID || "",
|
|
93
|
+
isPaired: true
|
|
94
|
+
// If we can get info, device is paired
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async function listDevices() {
|
|
98
|
+
try {
|
|
99
|
+
const result = await runIDeviceTool("idevice_id", ["-l"]);
|
|
100
|
+
if (!result) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
return parseDeviceList(result.stdout);
|
|
104
|
+
} catch {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function runIDeviceTool(toolName, args = [], options = {}) {
|
|
109
|
+
const command = `"${toolName}${process.platform === "win32" ? ".exe" : ""}" ${args.map((a) => `"${a}"`).join(" ")}`;
|
|
110
|
+
return execIDevice(command, options);
|
|
111
|
+
}
|
|
112
|
+
async function execIDevice(command, options = {}) {
|
|
113
|
+
const binPath = getIDeviceBinPath();
|
|
114
|
+
options.cwd = binPath;
|
|
115
|
+
const result = await execAsync(command, {
|
|
116
|
+
...options,
|
|
117
|
+
env: process.env,
|
|
118
|
+
windowsHide: true,
|
|
119
|
+
encoding: "utf8"
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
stdout: result.stdout.toString(),
|
|
123
|
+
stderr: result.stderr.toString()
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function getResourcesBinPath() {
|
|
127
|
+
const binPath = path.resolve(
|
|
128
|
+
process.env.IDeviceBinPath,
|
|
129
|
+
"bin",
|
|
130
|
+
process.platform === "win32" ?
|
|
131
|
+
"windows" :
|
|
132
|
+
process.platform === "darwin" ?
|
|
133
|
+
"darwin" :
|
|
134
|
+
"linux",
|
|
135
|
+
);
|
|
136
|
+
if (!binPath) {
|
|
137
|
+
throw new Error("IDeviceBinPath is not set");
|
|
138
|
+
}
|
|
139
|
+
return binPath;
|
|
140
|
+
}
|
|
141
|
+
function getIDeviceBinPath() {
|
|
142
|
+
return getResourcesBinPath();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/logic/actions/activation.ts
|
|
146
|
+
async function getActivationState(udid) {
|
|
147
|
+
logTask(`Getting activation state for device ${udid}`);
|
|
148
|
+
try {
|
|
149
|
+
const result = await runIDeviceTool("ideviceinfo", ["-u", udid, "-k", "ActivationState"]);
|
|
150
|
+
if (!result) {
|
|
151
|
+
return {
|
|
152
|
+
isActivated: false,
|
|
153
|
+
activationState: "Unknown"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const { stdout } = result;
|
|
157
|
+
const state = stdout.trim();
|
|
158
|
+
return {
|
|
159
|
+
isActivated: state === "Activated",
|
|
160
|
+
activationState: state
|
|
161
|
+
};
|
|
162
|
+
} catch {
|
|
163
|
+
return {
|
|
164
|
+
isActivated: false,
|
|
165
|
+
activationState: "Unknown"
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async function activate(udid) {
|
|
170
|
+
logTask(`Activating device ${udid}`);
|
|
171
|
+
try {
|
|
172
|
+
const result = await runIDeviceTool("ideviceactivation", ["-u", udid, "activate"]);
|
|
173
|
+
if (!result) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return result.stdout.toLowerCase().includes("success") || result.stdout.toLowerCase().includes("activated");
|
|
177
|
+
} catch (error) {
|
|
178
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
179
|
+
throw new Error(`Activation failed: ${errorMsg}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/logic/actions/pair.ts
|
|
184
|
+
async function isPaired(udid) {
|
|
185
|
+
logTask(`Checking pairing status for ${udid}`);
|
|
186
|
+
try {
|
|
187
|
+
const result = await runIDeviceTool("idevicepair", [
|
|
188
|
+
"-u",
|
|
189
|
+
udid,
|
|
190
|
+
"validate"
|
|
191
|
+
]);
|
|
192
|
+
if (!result) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
return result.stdout.toLowerCase().includes("success");
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function trustDevice(udid, timeout = 6e4, onWaitingForTrust) {
|
|
201
|
+
logTask(`Trusting device ${udid}`);
|
|
202
|
+
if (await isPaired(udid)) {
|
|
203
|
+
logInfo(`Device ${udid} is already trusted`);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
logInfo(`Initiating pairing for device ${udid}`);
|
|
207
|
+
const pairResult = await pair(udid);
|
|
208
|
+
if (pairResult) {
|
|
209
|
+
logInfo(`Device ${udid} paired successfully`);
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
logInfo("Please accept the trust dialog on the device...");
|
|
213
|
+
onWaitingForTrust?.();
|
|
214
|
+
try {
|
|
215
|
+
await waitForPairing(udid, timeout, 1e3);
|
|
216
|
+
logInfo(`Device ${udid} is now trusted`);
|
|
217
|
+
return true;
|
|
218
|
+
} catch {
|
|
219
|
+
logInfo(`Timeout waiting for trust acceptance on device ${udid}`);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async function waitForPairing(udid, timeout = 12e4, pollInterval = 1e3) {
|
|
224
|
+
logTask(`Waiting for pairing on device ${udid}`);
|
|
225
|
+
const startTime = Date.now();
|
|
226
|
+
while (Date.now() - startTime < timeout) {
|
|
227
|
+
if (await isPaired(udid)) {
|
|
228
|
+
logInfo(`Device ${udid} is now paired`);
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
232
|
+
}
|
|
233
|
+
throw new Error(`Timeout waiting for device pairing after ${timeout}ms`);
|
|
234
|
+
}
|
|
235
|
+
async function pair(udid) {
|
|
236
|
+
logTask(`Initiating pairing for device ${udid}`);
|
|
237
|
+
try {
|
|
238
|
+
const result = await runIDeviceTool("idevicepair", ["-u", udid, "pair"]);
|
|
239
|
+
if (!result) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
return result.stdout.toLowerCase().includes("success");
|
|
243
|
+
} catch (error) {
|
|
244
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
245
|
+
if (errorMsg.includes("Please accept the trust dialog")) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function unpair(udid) {
|
|
252
|
+
logTask(`Un-pairing device ${udid}`);
|
|
253
|
+
try {
|
|
254
|
+
const result = await runIDeviceTool("idevicepair", [
|
|
255
|
+
"-u",
|
|
256
|
+
udid,
|
|
257
|
+
"unpair"
|
|
258
|
+
]);
|
|
259
|
+
if (!result) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
return result.stdout.toLowerCase().includes("success");
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/logic/actions/install.ts
|
|
269
|
+
async function installApp(ipaPath, udid) {
|
|
270
|
+
logTask(`Installing app ${ipaPath} on device ${udid}`);
|
|
271
|
+
if (!await isPaired(udid)) {
|
|
272
|
+
await waitForPairing(udid, 1e4);
|
|
273
|
+
}
|
|
274
|
+
await runIDeviceTool("ideviceinstaller", ["-u", udid, "-i", ipaPath]);
|
|
275
|
+
}
|
|
276
|
+
async function uninstallApp(bundleId, udid) {
|
|
277
|
+
logTask(`Uninstalling app ${bundleId} from device ${udid}`);
|
|
278
|
+
if (!await isPaired(udid)) {
|
|
279
|
+
await waitForPairing(udid, 1e4);
|
|
280
|
+
}
|
|
281
|
+
await runIDeviceTool("ideviceinstaller", ["-u", udid, "-U", bundleId]);
|
|
282
|
+
}
|
|
283
|
+
async function listApps(udid) {
|
|
284
|
+
logTask(`Listing apps on device ${udid}`);
|
|
285
|
+
if (!await isPaired(udid)) {
|
|
286
|
+
await waitForPairing(udid, 1e4);
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const result = await runIDeviceTool("ideviceinstaller", ["-u", udid, "-l"]);
|
|
290
|
+
if (!result) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
const { stdout } = result;
|
|
294
|
+
return parseAppList(stdout);
|
|
295
|
+
} catch {
|
|
296
|
+
return [];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function isAppInstalled(bundleId, udid) {
|
|
300
|
+
logTask(`Checking if app ${bundleId} is installed on device ${udid}`);
|
|
301
|
+
const apps = await listApps(udid);
|
|
302
|
+
return apps.some((app) => app.bundleId === bundleId);
|
|
303
|
+
}
|
|
304
|
+
async function wakeDevice(udid) {
|
|
305
|
+
try {
|
|
306
|
+
logInfo("Attempting to wake device screen...");
|
|
307
|
+
await runIDeviceTool("ideviceinfo", ["-u", udid, "-k", "DeviceName"]);
|
|
308
|
+
try {
|
|
309
|
+
await runIDeviceTool("ideviceinfo", ["-u", udid, "-k", "ActivationState"]);
|
|
310
|
+
} catch {
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
await runIDeviceTool("idevicepair", ["-u", udid, "validate"]);
|
|
314
|
+
} catch {
|
|
315
|
+
}
|
|
316
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
317
|
+
logInfo("Device wake attempt completed");
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logInfo(
|
|
320
|
+
`Device wake attempt failed (non-critical): ${error instanceof Error ? error.message : String(error)}`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function launchApp(bundleId, args, udid) {
|
|
325
|
+
logTask(`Launching app ${bundleId} on device ${udid}`);
|
|
326
|
+
if (!await isPaired(udid)) {
|
|
327
|
+
await waitForPairing(udid, 1e4);
|
|
328
|
+
}
|
|
329
|
+
await wakeDevice(udid);
|
|
330
|
+
try {
|
|
331
|
+
logInfo(`Attempting to launch ${bundleId} using idevicedebug...`);
|
|
332
|
+
const result = await runIDeviceTool("idevicedebug", ["-u", udid, "run", bundleId, ...args]);
|
|
333
|
+
const output = (result?.stdout ?? "") + (result?.stderr ?? "");
|
|
334
|
+
if (output.trim()) {
|
|
335
|
+
logInfo(`idevicedebug output: ${output.substring(0, 200)}`);
|
|
336
|
+
}
|
|
337
|
+
if (output.toLowerCase().includes("could not start") && output.toLowerCase().includes("debugserver")) {
|
|
338
|
+
logInfo("idevicedebug requires debugserver, falling back to pymobiledevice3...");
|
|
339
|
+
throw new Error("debugserver_not_available");
|
|
340
|
+
}
|
|
341
|
+
logInfo(`App ${bundleId} launched successfully using idevicedebug`);
|
|
342
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
343
|
+
return;
|
|
344
|
+
} catch (error) {
|
|
345
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
346
|
+
if (errorMsg.includes("debugserver_not_available") || errorMsg.toLowerCase().includes("could not start") || errorMsg.toLowerCase().includes("debugserver")) {
|
|
347
|
+
logInfo("idevicedebug failed, trying pymobiledevice3 for iOS 17+...");
|
|
348
|
+
await launchAppWithPymobiledevice3(bundleId, args, udid);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async function launchAppWithPymobiledevice3(bundleId, args, udid) {
|
|
355
|
+
logTask(`Launching app ${bundleId} using pymobiledevice3`);
|
|
356
|
+
const { exec } = await import("node:child_process");
|
|
357
|
+
const { promisify: promisify2 } = await import("node:util");
|
|
358
|
+
const execAsync2 = promisify2(exec);
|
|
359
|
+
try {
|
|
360
|
+
let cmdArgs = [
|
|
361
|
+
"-m",
|
|
362
|
+
"pymobiledevice3",
|
|
363
|
+
"developer",
|
|
364
|
+
"dvt",
|
|
365
|
+
"launch",
|
|
366
|
+
"--udid",
|
|
367
|
+
udid,
|
|
368
|
+
"--kill-existing",
|
|
369
|
+
// Kill existing instance to bring app to foreground
|
|
370
|
+
bundleId,
|
|
371
|
+
...args
|
|
372
|
+
];
|
|
373
|
+
let command = `python ${cmdArgs.map((a) => `"${a}"`).join(" ")}`;
|
|
374
|
+
logInfo(`Executing: ${command}`);
|
|
375
|
+
const result = await execAsync2(command, {
|
|
376
|
+
windowsHide: true,
|
|
377
|
+
encoding: "utf8",
|
|
378
|
+
timeout: 3e4
|
|
379
|
+
// 30 second timeout
|
|
380
|
+
});
|
|
381
|
+
const output = result.stdout.toString() + result.stderr.toString();
|
|
382
|
+
if (output.trim()) {
|
|
383
|
+
logInfo(`pymobiledevice3 output: ${output.substring(0, 200)}`);
|
|
384
|
+
}
|
|
385
|
+
if (output.toLowerCase().includes("developer mode") && output.toLowerCase().includes("not enabled")) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
"Developer Mode is not enabled on the device.\nPlease enable it: Settings \u2192 Privacy & Security \u2192 Developer Mode \u2192 Enable"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
if (output.toLowerCase().includes("tunneld") && (output.toLowerCase().includes("unable to connect") || output.toLowerCase().includes("invalidserviceerror"))) {
|
|
391
|
+
logInfo("Tunnel required, retrying with tunnel option...");
|
|
392
|
+
cmdArgs = [
|
|
393
|
+
"-m",
|
|
394
|
+
"pymobiledevice3",
|
|
395
|
+
"developer",
|
|
396
|
+
"dvt",
|
|
397
|
+
"launch",
|
|
398
|
+
"--udid",
|
|
399
|
+
udid,
|
|
400
|
+
"--tunnel",
|
|
401
|
+
udid,
|
|
402
|
+
// Use UDID for tunnel
|
|
403
|
+
"--kill-existing",
|
|
404
|
+
bundleId,
|
|
405
|
+
...args
|
|
406
|
+
];
|
|
407
|
+
command = `python ${cmdArgs.map((a) => `"${a}"`).join(" ")}`;
|
|
408
|
+
logInfo(`Retrying with tunnel: ${command}`);
|
|
409
|
+
try {
|
|
410
|
+
const retryResult = await execAsync2(command, {
|
|
411
|
+
windowsHide: true,
|
|
412
|
+
encoding: "utf8",
|
|
413
|
+
timeout: 3e4
|
|
414
|
+
});
|
|
415
|
+
const retryOutput = retryResult.stdout.toString() + retryResult.stderr.toString();
|
|
416
|
+
if (retryOutput.trim()) {
|
|
417
|
+
logInfo(`pymobiledevice3 retry output: ${retryOutput.substring(0, 200)}`);
|
|
418
|
+
}
|
|
419
|
+
if (retryOutput.toLowerCase().includes("tunneld") && retryOutput.toLowerCase().includes("unable to connect")) {
|
|
420
|
+
throw new Error(
|
|
421
|
+
"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."
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
logInfo(`App ${bundleId} launched successfully using pymobiledevice3 with tunnel`);
|
|
425
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
426
|
+
return;
|
|
427
|
+
} catch {
|
|
428
|
+
throw new Error(
|
|
429
|
+
"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."
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (output.toLowerCase().includes("not found") || output.toLowerCase().includes("command not found") || output.toLowerCase().includes("cannot find")) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
"pymobiledevice3 is not installed.\nInstall it with: python -m pip install --user pymobiledevice3"
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
if (output.toLowerCase().includes("error") && !output.toLowerCase().includes("warning")) {
|
|
439
|
+
logInfo(`Warning: pymobiledevice3 reported errors: ${output.substring(0, 300)}`);
|
|
440
|
+
}
|
|
441
|
+
logInfo(`App ${bundleId} launched successfully using pymobiledevice3`);
|
|
442
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
443
|
+
} catch (error) {
|
|
444
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
445
|
+
if (errorMsg.includes("not found") || errorMsg.includes("command not found") || errorMsg.includes("cannot find") || errorMsg.includes("No module named")) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
"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."
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/logic/actions/proxy.ts
|
|
455
|
+
async function startPortForward(localPort, devicePort, udid) {
|
|
456
|
+
logTask(`Starting port forward ${localPort} -> ${devicePort} for device ${udid}`);
|
|
457
|
+
const result = await runIDeviceTool("iproxy", [
|
|
458
|
+
localPort.toString(),
|
|
459
|
+
devicePort.toString(),
|
|
460
|
+
"-u",
|
|
461
|
+
udid
|
|
462
|
+
]);
|
|
463
|
+
return {
|
|
464
|
+
localPort,
|
|
465
|
+
devicePort,
|
|
466
|
+
...result
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
async function startPortForwardAsync(localPort, devicePort, udid, _timeout = 5e3) {
|
|
470
|
+
const result = await startPortForward(localPort, devicePort, udid);
|
|
471
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
472
|
+
if (result.stdout.includes("error") || result.stderr.includes("error")) {
|
|
473
|
+
throw new Error("Port forwarding failed to start");
|
|
474
|
+
}
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/logic/appleDeviceKit.ts
|
|
128
479
|
var AppleDeviceKit = class {
|
|
129
480
|
constructor(udid, port) {
|
|
130
481
|
this.port = port;
|
|
@@ -132,314 +483,127 @@ var AppleDeviceKit = class {
|
|
|
132
483
|
logInfo(`AppleDeviceKit initialized for device: ${this.deviceId}`);
|
|
133
484
|
}
|
|
134
485
|
deviceId;
|
|
135
|
-
// ==================== Device Info ====================
|
|
136
486
|
/**
|
|
137
487
|
* Get detailed device information
|
|
138
488
|
*/
|
|
139
489
|
async getDeviceInfo() {
|
|
140
|
-
|
|
141
|
-
const toolPath = getIDeviceToolPath("ideviceinfo");
|
|
142
|
-
if (!toolPath) {
|
|
143
|
-
throw new Error("ideviceinfo tool not found");
|
|
144
|
-
}
|
|
145
|
-
const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId]);
|
|
146
|
-
const props = parsePlistOutput(stdout);
|
|
147
|
-
return {
|
|
148
|
-
deviceName: props.DeviceName || "",
|
|
149
|
-
productType: props.ProductType || "",
|
|
150
|
-
productVersion: props.ProductVersion || "",
|
|
151
|
-
buildVersion: props.BuildVersion || "",
|
|
152
|
-
serialNumber: props.SerialNumber || "",
|
|
153
|
-
udid: props.UniqueDeviceID || this.deviceId,
|
|
154
|
-
wifiAddress: props.WiFiAddress || "",
|
|
155
|
-
bluetoothAddress: props.BluetoothAddress || "",
|
|
156
|
-
phoneNumber: props.PhoneNumber || "",
|
|
157
|
-
cpuArchitecture: props.CPUArchitecture || "",
|
|
158
|
-
hardwareModel: props.HardwareModel || "",
|
|
159
|
-
modelNumber: props.ModelNumber || "",
|
|
160
|
-
regionInfo: props.RegionInfo || "",
|
|
161
|
-
timeZone: props.TimeZone || "",
|
|
162
|
-
uniqueChipID: props.UniqueChipID || "",
|
|
163
|
-
isPaired: true
|
|
164
|
-
// If we can get info, device is paired
|
|
165
|
-
};
|
|
490
|
+
return getDeviceInfo(this.deviceId);
|
|
166
491
|
}
|
|
167
|
-
// ==================== Trust/Pairing ====================
|
|
168
492
|
/**
|
|
169
493
|
* Check if device is paired/trusted with this computer
|
|
170
494
|
*/
|
|
171
495
|
async isPaired() {
|
|
172
|
-
|
|
173
|
-
const toolPath = getIDeviceToolPath("idevicepair");
|
|
174
|
-
if (!toolPath) {
|
|
175
|
-
throw new Error("idevicepair tool not found");
|
|
176
|
-
}
|
|
177
|
-
try {
|
|
178
|
-
const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "validate"]);
|
|
179
|
-
return stdout.toLowerCase().includes("success");
|
|
180
|
-
} catch {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
496
|
+
return isPaired(this.deviceId);
|
|
183
497
|
}
|
|
184
498
|
/**
|
|
185
499
|
* Wait for device to be paired
|
|
186
500
|
* Polls the pairing status until successful or timeout
|
|
187
|
-
*
|
|
501
|
+
*
|
|
188
502
|
* @param timeout Timeout in milliseconds (default: 120000)
|
|
189
503
|
* @param pollInterval Poll interval in milliseconds (default: 1000)
|
|
190
504
|
*/
|
|
191
505
|
async waitForPairing(timeout = 12e4, pollInterval = 1e3) {
|
|
192
|
-
|
|
193
|
-
const startTime = Date.now();
|
|
194
|
-
while (Date.now() - startTime < timeout) {
|
|
195
|
-
if (await this.isPaired()) {
|
|
196
|
-
logInfo(`Device ${this.deviceId} is now paired`);
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
200
|
-
}
|
|
201
|
-
throw new Error(`Timeout waiting for device pairing after ${timeout}ms`);
|
|
506
|
+
return waitForPairing(this.deviceId, timeout, pollInterval);
|
|
202
507
|
}
|
|
203
508
|
/**
|
|
204
509
|
* Attempt to pair/trust the device
|
|
205
510
|
* User must accept the trust dialog on the device
|
|
206
511
|
*/
|
|
207
512
|
async pair() {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
513
|
+
return pair(this.deviceId);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Trust/pair the device - initiates pairing and waits for user to accept
|
|
517
|
+
*
|
|
518
|
+
* This is the recommended method for establishing trust with a device.
|
|
519
|
+
* It will:
|
|
520
|
+
* 1. Check if already paired
|
|
521
|
+
* 2. If not, initiate pairing (shows "Trust This Computer?" on device)
|
|
522
|
+
* 3. Wait for user to accept the trust dialog
|
|
523
|
+
*
|
|
524
|
+
* @param timeout Timeout in milliseconds to wait for user acceptance (default: 60000)
|
|
525
|
+
* @param onWaitingForTrust Callback when waiting for user to accept trust dialog
|
|
526
|
+
* @returns true if device is now trusted
|
|
527
|
+
*/
|
|
528
|
+
async trustDevice(timeout = 6e4, onWaitingForTrust) {
|
|
529
|
+
return trustDevice(this.deviceId, timeout, onWaitingForTrust);
|
|
223
530
|
}
|
|
224
531
|
/**
|
|
225
532
|
* Unpair/untrust the device
|
|
226
533
|
*/
|
|
227
534
|
async unpair() {
|
|
228
|
-
|
|
229
|
-
const toolPath = getIDeviceToolPath("idevicepair");
|
|
230
|
-
if (!toolPath) {
|
|
231
|
-
throw new Error("idevicepair tool not found");
|
|
232
|
-
}
|
|
233
|
-
try {
|
|
234
|
-
const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "unpair"]);
|
|
235
|
-
return stdout.toLowerCase().includes("success");
|
|
236
|
-
} catch {
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
535
|
+
return unpair(this.deviceId);
|
|
239
536
|
}
|
|
240
|
-
// ==================== App Installation ====================
|
|
241
537
|
/**
|
|
242
538
|
* Install an IPA file on the device (install agent)
|
|
243
|
-
*
|
|
539
|
+
*
|
|
244
540
|
* @param ipaPath Path to the IPA file
|
|
245
541
|
*/
|
|
246
542
|
async installApp(ipaPath) {
|
|
247
|
-
|
|
248
|
-
if (!await this.isPaired()) {
|
|
249
|
-
await this.waitForPairing(1e4);
|
|
250
|
-
}
|
|
251
|
-
const toolPath = getIDeviceToolPath("ideviceinstaller");
|
|
252
|
-
if (!toolPath) {
|
|
253
|
-
throw new Error("ideviceinstaller tool not found");
|
|
254
|
-
}
|
|
255
|
-
await runIDeviceTool(toolPath, ["-u", this.deviceId, "-i", ipaPath]);
|
|
543
|
+
installApp(ipaPath, this.deviceId);
|
|
256
544
|
}
|
|
257
545
|
/**
|
|
258
546
|
* Uninstall an app by bundle ID (uninstall agent)
|
|
259
|
-
*
|
|
547
|
+
*
|
|
260
548
|
* @param bundleId Application bundle identifier
|
|
261
549
|
*/
|
|
262
550
|
async uninstallApp(bundleId) {
|
|
263
|
-
|
|
264
|
-
if (!await this.isPaired()) {
|
|
265
|
-
await this.waitForPairing(1e4);
|
|
266
|
-
}
|
|
267
|
-
const toolPath = getIDeviceToolPath("ideviceinstaller");
|
|
268
|
-
if (!toolPath) {
|
|
269
|
-
throw new Error("ideviceinstaller tool not found");
|
|
270
|
-
}
|
|
271
|
-
await runIDeviceTool(toolPath, ["-u", this.deviceId, "-U", bundleId]);
|
|
551
|
+
uninstallApp(bundleId, this.deviceId);
|
|
272
552
|
}
|
|
273
553
|
/**
|
|
274
554
|
* Check if an app is installed on the device
|
|
275
|
-
*
|
|
555
|
+
*
|
|
276
556
|
* @param bundleId Application bundle identifier
|
|
277
557
|
*/
|
|
278
558
|
async isAppInstalled(bundleId) {
|
|
279
|
-
|
|
280
|
-
const apps = await this.listApps();
|
|
281
|
-
return apps.some((app) => app.bundleId === bundleId);
|
|
559
|
+
return isAppInstalled(bundleId, this.deviceId);
|
|
282
560
|
}
|
|
283
561
|
/**
|
|
284
562
|
* List all installed user applications
|
|
285
563
|
*/
|
|
286
564
|
async listApps() {
|
|
287
|
-
|
|
288
|
-
if (!await this.isPaired()) {
|
|
289
|
-
await this.waitForPairing(1e4);
|
|
290
|
-
}
|
|
291
|
-
const toolPath = getIDeviceToolPath("ideviceinstaller");
|
|
292
|
-
if (!toolPath) {
|
|
293
|
-
throw new Error("ideviceinstaller tool not found");
|
|
294
|
-
}
|
|
295
|
-
try {
|
|
296
|
-
const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "-l"]);
|
|
297
|
-
return parseAppList(stdout);
|
|
298
|
-
} catch {
|
|
299
|
-
return [];
|
|
300
|
-
}
|
|
565
|
+
return listApps(this.deviceId);
|
|
301
566
|
}
|
|
302
|
-
// ==================== Launch Application ====================
|
|
303
567
|
/**
|
|
304
568
|
* Launch an application on the device
|
|
305
|
-
*
|
|
306
|
-
* @param bundleId Application bundle identifier
|
|
307
|
-
* @param args
|
|
569
|
+
*
|
|
570
|
+
* @param bundleId Application bundle identifier
|
|
571
|
+
* @param args Application arguments
|
|
308
572
|
*/
|
|
309
573
|
async launchApp(bundleId, args = []) {
|
|
310
|
-
|
|
311
|
-
if (!await this.isPaired()) {
|
|
312
|
-
await this.waitForPairing(1e4);
|
|
313
|
-
}
|
|
314
|
-
const toolPath = getIDeviceToolPath("idevicedebug");
|
|
315
|
-
if (!toolPath) {
|
|
316
|
-
throw new Error("idevicedebug tool not found");
|
|
317
|
-
}
|
|
318
|
-
const toolArgs = ["-u", this.deviceId, "run", bundleId, ...args];
|
|
319
|
-
await runIDeviceTool(toolPath, toolArgs);
|
|
320
|
-
}
|
|
321
|
-
// ==================== Port Forwarding ====================
|
|
322
|
-
/**
|
|
323
|
-
* Start port forwarding from local port to device port
|
|
324
|
-
*
|
|
325
|
-
* @param localPort Local port to listen on
|
|
326
|
-
* @param devicePort Device port to forward to
|
|
327
|
-
* @returns Handle to stop the port forwarding
|
|
328
|
-
*/
|
|
329
|
-
startPortForward(localPort, devicePort) {
|
|
330
|
-
logTask(`Starting port forward ${localPort} -> ${devicePort} for device ${this.deviceId}`);
|
|
331
|
-
const toolPath = getIDeviceToolPath("iproxy");
|
|
332
|
-
if (!toolPath) {
|
|
333
|
-
throw new Error("iproxy tool not found");
|
|
334
|
-
}
|
|
335
|
-
const binPath = getIDeviceBinPath();
|
|
336
|
-
const env = {
|
|
337
|
-
...process.env,
|
|
338
|
-
PATH: `${binPath};${process.env.PATH}`
|
|
339
|
-
};
|
|
340
|
-
const proc = spawn(toolPath, [
|
|
341
|
-
localPort.toString(),
|
|
342
|
-
devicePort.toString(),
|
|
343
|
-
"-u",
|
|
344
|
-
this.deviceId
|
|
345
|
-
], {
|
|
346
|
-
env,
|
|
347
|
-
windowsHide: true,
|
|
348
|
-
stdio: "pipe"
|
|
349
|
-
});
|
|
350
|
-
proc.on("error", (err) => {
|
|
351
|
-
logInfo(`Port forward error: ${err.message}`);
|
|
352
|
-
});
|
|
353
|
-
return {
|
|
354
|
-
stop: () => {
|
|
355
|
-
proc.kill();
|
|
356
|
-
},
|
|
357
|
-
localPort,
|
|
358
|
-
devicePort,
|
|
359
|
-
process: proc
|
|
360
|
-
};
|
|
574
|
+
return launchApp(bundleId, args, this.deviceId);
|
|
361
575
|
}
|
|
362
576
|
/**
|
|
363
|
-
* Start port forwarding and wait for it to be ready
|
|
364
|
-
*
|
|
577
|
+
* Start port forwarding and wait for it to be ready.
|
|
578
|
+
* we need port forwarding to be able to connect to the device from the computer
|
|
579
|
+
* and communicate with it using the local port.
|
|
580
|
+
*
|
|
365
581
|
* @param localPort Local port to listen on
|
|
366
582
|
* @param devicePort Device port to forward to
|
|
367
583
|
* @param _timeout Timeout in milliseconds (reserved for future use)
|
|
368
584
|
*/
|
|
369
585
|
async startPortForwardAsync(localPort, devicePort, _timeout = 5e3) {
|
|
370
|
-
|
|
371
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
372
|
-
if (handle.process.killed || handle.process.exitCode !== null) {
|
|
373
|
-
throw new Error("Port forwarding failed to start");
|
|
374
|
-
}
|
|
375
|
-
return handle;
|
|
586
|
+
return startPortForwardAsync(localPort, devicePort, this.deviceId);
|
|
376
587
|
}
|
|
377
|
-
// ==================== Activation ====================
|
|
378
588
|
/**
|
|
379
589
|
* Get the activation state of the device
|
|
380
590
|
*/
|
|
381
591
|
async getActivationState() {
|
|
382
|
-
|
|
383
|
-
const toolPath = getIDeviceToolPath("ideviceinfo");
|
|
384
|
-
if (!toolPath) {
|
|
385
|
-
throw new Error("ideviceinfo tool not found");
|
|
386
|
-
}
|
|
387
|
-
try {
|
|
388
|
-
const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "-k", "ActivationState"]);
|
|
389
|
-
const state = stdout.trim();
|
|
390
|
-
return {
|
|
391
|
-
isActivated: state === "Activated",
|
|
392
|
-
activationState: state
|
|
393
|
-
};
|
|
394
|
-
} catch {
|
|
395
|
-
return {
|
|
396
|
-
isActivated: false,
|
|
397
|
-
activationState: "Unknown"
|
|
398
|
-
};
|
|
399
|
-
}
|
|
592
|
+
return getActivationState(this.deviceId);
|
|
400
593
|
}
|
|
401
594
|
/**
|
|
402
|
-
* Activate the device
|
|
595
|
+
* Activate the device (register in apple servers), an activated device belongs to someone (a user/company).
|
|
596
|
+
* A device that is on hello screen cannot pass the wifi step unless it is activated.
|
|
597
|
+
* the activate save us the need of the device to be connected to the internet to do the activation
|
|
598
|
+
* and register in apple servers.
|
|
599
|
+
*
|
|
403
600
|
* Note: This requires a valid activation record or Apple server access
|
|
601
|
+
*
|
|
602
|
+
* precondition: the device must be paired and trusted
|
|
404
603
|
*/
|
|
405
604
|
async activate() {
|
|
406
|
-
|
|
407
|
-
const toolPath = getIDeviceToolPath("ideviceactivation");
|
|
408
|
-
if (!toolPath) {
|
|
409
|
-
throw new Error("ideviceactivation tool not found");
|
|
410
|
-
}
|
|
411
|
-
try {
|
|
412
|
-
const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "activate"]);
|
|
413
|
-
return stdout.toLowerCase().includes("success") || stdout.toLowerCase().includes("activated");
|
|
414
|
-
} catch (error) {
|
|
415
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
416
|
-
throw new Error(`Activation failed: ${errorMsg}`);
|
|
417
|
-
}
|
|
605
|
+
return activate(this.deviceId);
|
|
418
606
|
}
|
|
419
|
-
/**
|
|
420
|
-
* Deactivate the device
|
|
421
|
-
*/
|
|
422
|
-
async deactivate() {
|
|
423
|
-
logTask(`Deactivating device ${this.deviceId}`);
|
|
424
|
-
const toolPath = getIDeviceToolPath("ideviceactivation");
|
|
425
|
-
if (!toolPath) {
|
|
426
|
-
throw new Error("ideviceactivation tool not found");
|
|
427
|
-
}
|
|
428
|
-
try {
|
|
429
|
-
const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "deactivate"]);
|
|
430
|
-
return stdout.toLowerCase().includes("success");
|
|
431
|
-
} catch {
|
|
432
|
-
return false;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Get activation state as string
|
|
437
|
-
*/
|
|
438
|
-
async getActivationStateString() {
|
|
439
|
-
const state = await this.getActivationState();
|
|
440
|
-
return state.activationState;
|
|
441
|
-
}
|
|
442
|
-
// ==================== Device Identifiers ====================
|
|
443
607
|
/**
|
|
444
608
|
* Get the device UDID
|
|
445
609
|
*/
|
|
@@ -452,22 +616,6 @@ var AppleDeviceKit = class {
|
|
|
452
616
|
getPort() {
|
|
453
617
|
return this.port;
|
|
454
618
|
}
|
|
455
|
-
// ==================== Static Methods ====================
|
|
456
|
-
/**
|
|
457
|
-
* Static method to list all connected iOS devices
|
|
458
|
-
*/
|
|
459
|
-
static async listDevices() {
|
|
460
|
-
const toolPath = getIDeviceToolPath("idevice_id");
|
|
461
|
-
if (!toolPath) {
|
|
462
|
-
throw new Error("idevice_id tool not found");
|
|
463
|
-
}
|
|
464
|
-
try {
|
|
465
|
-
const { stdout } = await runIDeviceTool(toolPath, ["-l"]);
|
|
466
|
-
return parseDeviceList(stdout);
|
|
467
|
-
} catch {
|
|
468
|
-
return [];
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
619
|
};
|
|
472
620
|
|
|
473
621
|
// src/logic/devicesMonitor.ts
|
|
@@ -483,7 +631,7 @@ var DevicesMonitor = class {
|
|
|
483
631
|
eventEmitter;
|
|
484
632
|
/**
|
|
485
633
|
* Start tracking iOS device connections
|
|
486
|
-
*
|
|
634
|
+
*
|
|
487
635
|
* @returns EventEmitter that emits:
|
|
488
636
|
* - 'added': (deviceKit: AppleDeviceKit) when a device is connected
|
|
489
637
|
* - 'removed': (deviceId: string, port: number) when a device is disconnected
|
|
@@ -493,9 +641,7 @@ var DevicesMonitor = class {
|
|
|
493
641
|
const listenerConfig = {
|
|
494
642
|
logicalPortMap: this.config.logicalPortMap,
|
|
495
643
|
// Filter for Apple devices only
|
|
496
|
-
listenOnlyDevices: [
|
|
497
|
-
{ vid: APPLE_VID.toString(16).toUpperCase().padStart(4, "0"), pid: "" }
|
|
498
|
-
]
|
|
644
|
+
listenOnlyDevices: [{ vid: APPLE_VID.toString(16).toUpperCase().padStart(4, "0"), pid: "" }]
|
|
499
645
|
};
|
|
500
646
|
usbDeviceListener.startListening(listenerConfig);
|
|
501
647
|
this.eventEmitter = new EventEmitter();
|
|
@@ -553,7 +699,7 @@ var DevicesMonitor = class {
|
|
|
553
699
|
* Find connected iOS device and emit event
|
|
554
700
|
*/
|
|
555
701
|
async findAndEmitDevice(usbDevice) {
|
|
556
|
-
const iOSDevices = await
|
|
702
|
+
const iOSDevices = await listDevices();
|
|
557
703
|
if (iOSDevices.length === 0) {
|
|
558
704
|
setTimeout(() => this.findAndEmitDevice(usbDevice), 500);
|
|
559
705
|
return;
|
|
@@ -576,7 +722,7 @@ var DevicesMonitor = class {
|
|
|
576
722
|
*/
|
|
577
723
|
async identifyConnectedDevices() {
|
|
578
724
|
logInfo("Identifying already connected iOS devices");
|
|
579
|
-
const iOSDevices = await
|
|
725
|
+
const iOSDevices = await listDevices();
|
|
580
726
|
const usbDevices = usbDeviceListener.listDevices().filter((d) => d.vid === APPLE_VID);
|
|
581
727
|
for (let i = 0; i < iOSDevices.length; i++) {
|
|
582
728
|
const iDevice = iOSDevices[i];
|
|
@@ -593,88 +739,8 @@ var DevicesMonitor = class {
|
|
|
593
739
|
}
|
|
594
740
|
}
|
|
595
741
|
};
|
|
596
|
-
|
|
597
|
-
// src/utils/usbmuxd.ts
|
|
598
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
599
|
-
var usbmuxdProcess = null;
|
|
600
|
-
function startUsbmuxd(foreground = false) {
|
|
601
|
-
if (usbmuxdProcess && !usbmuxdProcess.killed) {
|
|
602
|
-
logInfo("usbmuxd is already running");
|
|
603
|
-
return {
|
|
604
|
-
stop: () => stopUsbmuxd(),
|
|
605
|
-
isRunning: () => !usbmuxdProcess?.killed,
|
|
606
|
-
process: usbmuxdProcess
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
const toolPath = getIDeviceToolPath("usbmuxd");
|
|
610
|
-
if (!toolPath) {
|
|
611
|
-
throw new Error("usbmuxd executable not found");
|
|
612
|
-
}
|
|
613
|
-
const binPath = getIDeviceBinPath();
|
|
614
|
-
const env = {
|
|
615
|
-
...process.env,
|
|
616
|
-
PATH: `${binPath};${process.env.PATH}`
|
|
617
|
-
};
|
|
618
|
-
const args = [];
|
|
619
|
-
if (foreground) {
|
|
620
|
-
args.push("-f");
|
|
621
|
-
}
|
|
622
|
-
args.push("-v");
|
|
623
|
-
logInfo(`Starting usbmuxd: ${toolPath}`);
|
|
624
|
-
usbmuxdProcess = spawn2(toolPath, args, {
|
|
625
|
-
env,
|
|
626
|
-
windowsHide: true,
|
|
627
|
-
stdio: "pipe",
|
|
628
|
-
detached: !foreground
|
|
629
|
-
});
|
|
630
|
-
usbmuxdProcess.stdout?.on("data", (data) => {
|
|
631
|
-
logInfo(`usbmuxd: ${data.toString().trim()}`);
|
|
632
|
-
});
|
|
633
|
-
usbmuxdProcess.stderr?.on("data", (data) => {
|
|
634
|
-
logInfo(`usbmuxd error: ${data.toString().trim()}`);
|
|
635
|
-
});
|
|
636
|
-
usbmuxdProcess.on("error", (err) => {
|
|
637
|
-
logInfo(`usbmuxd failed to start: ${err.message}`);
|
|
638
|
-
});
|
|
639
|
-
usbmuxdProcess.on("exit", (code) => {
|
|
640
|
-
logInfo(`usbmuxd exited with code ${code}`);
|
|
641
|
-
usbmuxdProcess = null;
|
|
642
|
-
});
|
|
643
|
-
if (!foreground) {
|
|
644
|
-
usbmuxdProcess.unref();
|
|
645
|
-
}
|
|
646
|
-
return {
|
|
647
|
-
stop: () => stopUsbmuxd(),
|
|
648
|
-
isRunning: () => usbmuxdProcess !== null && !usbmuxdProcess.killed,
|
|
649
|
-
process: usbmuxdProcess
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
function stopUsbmuxd() {
|
|
653
|
-
if (usbmuxdProcess && !usbmuxdProcess.killed) {
|
|
654
|
-
logInfo("Stopping usbmuxd");
|
|
655
|
-
usbmuxdProcess.kill();
|
|
656
|
-
usbmuxdProcess = null;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
function isUsbmuxdRunning() {
|
|
660
|
-
return usbmuxdProcess !== null && !usbmuxdProcess.killed;
|
|
661
|
-
}
|
|
662
|
-
function ensureUsbmuxd() {
|
|
663
|
-
if (isUsbmuxdRunning()) {
|
|
664
|
-
return {
|
|
665
|
-
stop: () => stopUsbmuxd(),
|
|
666
|
-
isRunning: () => isUsbmuxdRunning(),
|
|
667
|
-
process: usbmuxdProcess
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
return startUsbmuxd();
|
|
671
|
-
}
|
|
672
742
|
export {
|
|
673
743
|
AppleDeviceKit,
|
|
674
|
-
DevicesMonitor
|
|
675
|
-
ensureUsbmuxd,
|
|
676
|
-
isUsbmuxdRunning,
|
|
677
|
-
startUsbmuxd,
|
|
678
|
-
stopUsbmuxd
|
|
744
|
+
DevicesMonitor
|
|
679
745
|
};
|
|
680
746
|
//# sourceMappingURL=index.js.map
|