@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.
Files changed (88) hide show
  1. package/dist/index.js +470 -404
  2. package/dist/index.js.map +4 -4
  3. package/dist/types/index.d.ts +6 -3
  4. package/dist/types/index.d.ts.map +1 -1
  5. package/dist/types/logic/actions/activation.d.ts +12 -0
  6. package/dist/types/logic/actions/activation.d.ts.map +1 -0
  7. package/dist/types/logic/actions/device.d.ts +27 -0
  8. package/dist/types/logic/actions/device.d.ts.map +1 -0
  9. package/dist/types/logic/actions/install.d.ts +9 -0
  10. package/dist/types/logic/actions/install.d.ts.map +1 -0
  11. package/dist/types/logic/actions/pair.d.ts +6 -0
  12. package/dist/types/logic/actions/pair.d.ts.map +1 -0
  13. package/dist/types/logic/actions/proxy.d.ts +3 -0
  14. package/dist/types/logic/actions/proxy.d.ts.map +1 -0
  15. package/dist/types/logic/appleDeviceKit.d.ts +53 -7
  16. package/dist/types/logic/appleDeviceKit.d.ts.map +1 -1
  17. package/dist/types/logic/dataParser.d.ts +23 -0
  18. package/dist/types/logic/dataParser.d.ts.map +1 -0
  19. package/dist/types/logic/devicesMonitor.d.ts.map +1 -1
  20. package/dist/types/logic/enrollment.d.ts +56 -0
  21. package/dist/types/logic/enrollment.d.ts.map +1 -0
  22. package/dist/types/logic/skipSetup.d.ts +81 -0
  23. package/dist/types/logic/skipSetup.d.ts.map +1 -0
  24. package/dist/types/types/skipSteps.d.ts +136 -0
  25. package/dist/types/types/skipSteps.d.ts.map +1 -0
  26. package/dist/types/types.d.ts +22 -5
  27. package/dist/types/types.d.ts.map +1 -1
  28. package/dist/types/utils/debug.d.ts +8 -0
  29. package/dist/types/utils/debug.d.ts.map +1 -1
  30. package/dist/types/utils/idevicePath.d.ts +3 -0
  31. package/dist/types/utils/idevicePath.d.ts.map +1 -1
  32. package/dist/types/utils/usbmuxd.d.ts +19 -0
  33. package/dist/types/utils/usbmuxd.d.ts.map +1 -0
  34. package/dist/types/utils/wifiProfile.d.ts +72 -0
  35. package/dist/types/utils/wifiProfile.d.ts.map +1 -0
  36. package/package.json +4 -3
  37. package/resources/bin/windows/ideviceactivation.exe +0 -0
  38. package/resources/bin/windows/libideviceactivation-1.0.dll +0 -0
  39. package/resources/bin/windows/libimobiledevice-1.0.dll +0 -0
  40. package/resources/bin/windows/libimobiledevice-glue-1.0.dll +0 -0
  41. package/resources/bin/windows/libplist-2.0.dll +0 -0
  42. package/resources/bin/windows/libusbmuxd-2.0.dll +0 -0
  43. package/resources/lib/include/libimobiledevice/afc.h +413 -0
  44. package/resources/lib/include/libimobiledevice/bt_packet_logger.h +156 -0
  45. package/resources/lib/include/libimobiledevice/companion_proxy.h +212 -0
  46. package/resources/lib/include/libimobiledevice/debugserver.h +272 -0
  47. package/resources/lib/include/libimobiledevice/diagnostics_relay.h +228 -0
  48. package/resources/lib/include/libimobiledevice/file_relay.h +166 -0
  49. package/resources/lib/include/libimobiledevice/heartbeat.h +137 -0
  50. package/resources/lib/include/libimobiledevice/house_arrest.h +180 -0
  51. package/resources/lib/include/libimobiledevice/installation_proxy.h +505 -0
  52. package/resources/lib/include/libimobiledevice/libimobiledevice.h +444 -0
  53. package/resources/lib/include/libimobiledevice/lockdown.h +577 -0
  54. package/resources/lib/include/libimobiledevice/misagent.h +168 -0
  55. package/resources/lib/include/libimobiledevice/mobile_image_mounter.h +275 -0
  56. package/resources/lib/include/libimobiledevice/mobileactivation.h +192 -0
  57. package/resources/lib/include/libimobiledevice/mobilebackup.h +246 -0
  58. package/resources/lib/include/libimobiledevice/mobilebackup2.h +214 -0
  59. package/resources/lib/include/libimobiledevice/mobilesync.h +359 -0
  60. package/resources/lib/include/libimobiledevice/notification_proxy.h +202 -0
  61. package/resources/lib/include/libimobiledevice/ostrace.h +198 -0
  62. package/resources/lib/include/libimobiledevice/preboard.h +187 -0
  63. package/resources/lib/include/libimobiledevice/property_list_service.h +184 -0
  64. package/resources/lib/include/libimobiledevice/restore.h +179 -0
  65. package/resources/lib/include/libimobiledevice/reverse_proxy.h +213 -0
  66. package/resources/lib/include/libimobiledevice/sbservices.h +175 -0
  67. package/resources/lib/include/libimobiledevice/screenshotr.h +118 -0
  68. package/resources/lib/include/libimobiledevice/service.h +202 -0
  69. package/resources/lib/include/libimobiledevice/syslog_relay.h +184 -0
  70. package/resources/lib/include/libimobiledevice/webinspector.h +137 -0
  71. package/resources/lib/include/plist/Array.h +80 -0
  72. package/resources/lib/include/plist/Boolean.h +48 -0
  73. package/resources/lib/include/plist/Data.h +50 -0
  74. package/resources/lib/include/plist/Date.h +54 -0
  75. package/resources/lib/include/plist/Dictionary.h +73 -0
  76. package/resources/lib/include/plist/Integer.h +54 -0
  77. package/resources/lib/include/plist/Key.h +49 -0
  78. package/resources/lib/include/plist/Node.h +57 -0
  79. package/resources/lib/include/plist/Real.h +48 -0
  80. package/resources/lib/include/plist/String.h +52 -0
  81. package/resources/lib/include/plist/Structure.h +62 -0
  82. package/resources/lib/include/plist/Uid.h +48 -0
  83. package/resources/lib/include/plist/plist++.h +39 -0
  84. package/resources/lib/include/plist/plist.h +1448 -0
  85. package/resources/lib/libimobiledevice-1.0.dll +0 -0
  86. package/resources/lib/libimobiledevice-1.0.dll.a +0 -0
  87. package/resources/lib/libplist-2.0.dll +0 -0
  88. 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/utils/exec.ts
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 { dirname, join } from "node:path";
22
- import { fileURLToPath } from "node:url";
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/appleDeviceKit.ts
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
- logTask(`Getting device info for ${this.deviceId}`);
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
- logTask(`Checking pairing status for ${this.deviceId}`);
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
- logTask(`Waiting for pairing on device ${this.deviceId}`);
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
- logTask(`Initiating pairing for device ${this.deviceId}`);
209
- const toolPath = getIDeviceToolPath("idevicepair");
210
- if (!toolPath) {
211
- throw new Error("idevicepair tool not found");
212
- }
213
- try {
214
- const { stdout } = await runIDeviceTool(toolPath, ["-u", this.deviceId, "pair"]);
215
- return stdout.toLowerCase().includes("success");
216
- } catch (error) {
217
- const errorMsg = error instanceof Error ? error.message : String(error);
218
- if (errorMsg.includes("Please accept the trust dialog")) {
219
- return false;
220
- }
221
- throw error;
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
- logTask(`Unpairing device ${this.deviceId}`);
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
- logTask(`Installing app ${ipaPath} on device ${this.deviceId}`);
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
- logTask(`Uninstalling app ${bundleId} from device ${this.deviceId}`);
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
- logTask(`Checking if app ${bundleId} is installed on device ${this.deviceId}`);
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
- logTask(`Listing apps on device ${this.deviceId}`);
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 to launch
307
- * @param args Optional arguments to pass to the app
569
+ *
570
+ * @param bundleId Application bundle identifier
571
+ * @param args Application arguments
308
572
  */
309
573
  async launchApp(bundleId, args = []) {
310
- logTask(`Launching app ${bundleId} on device ${this.deviceId}`);
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
- const handle = this.startPortForward(localPort, devicePort);
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
- logTask(`Getting activation state for device ${this.deviceId}`);
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
- logTask(`Activating device ${this.deviceId}`);
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 AppleDeviceKit.listDevices();
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 AppleDeviceKit.listDevices();
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