@mcesystems/apple-kit 1.0.0
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/README.md +250 -0
- package/dist/index.js +680 -0
- package/dist/index.js.map +7 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/logic/appleDeviceKit.d.ts +68 -0
- package/dist/types/logic/appleDeviceKit.d.ts.map +1 -0
- package/dist/types/logic/devicesMonitor.d.ts +45 -0
- package/dist/types/logic/devicesMonitor.d.ts.map +1 -0
- package/dist/types/types.d.ts +127 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/utils/debug.d.ts +9 -0
- package/dist/types/utils/debug.d.ts.map +1 -0
- package/dist/types/utils/exec.d.ts +14 -0
- package/dist/types/utils/exec.d.ts.map +1 -0
- package/dist/types/utils/idevicePath.d.ts +19 -0
- package/dist/types/utils/idevicePath.d.ts.map +1 -0
- package/package.json +63 -0
- package/resources/bin/windows/bz2.dll +0 -0
- package/resources/bin/windows/getopt.dll +0 -0
- package/resources/bin/windows/iconv-2.dll +0 -0
- package/resources/bin/windows/idevice_id.exe +0 -0
- package/resources/bin/windows/ideviceactivation.exe +0 -0
- package/resources/bin/windows/idevicedebug.exe +0 -0
- package/resources/bin/windows/ideviceinfo.exe +0 -0
- package/resources/bin/windows/ideviceinstaller.exe +0 -0
- package/resources/bin/windows/idevicepair.exe +0 -0
- package/resources/bin/windows/imobiledevice.dll +0 -0
- package/resources/bin/windows/iproxy.exe +0 -0
- package/resources/bin/windows/libcrypto-1_1-x64.dll +0 -0
- package/resources/bin/windows/libcurl.dll +0 -0
- package/resources/bin/windows/libssl-1_1-x64.dll +0 -0
- package/resources/bin/windows/libusb-1.0.dll +0 -0
- package/resources/bin/windows/libusb0.dll +0 -0
- package/resources/bin/windows/libxml2.dll +0 -0
- package/resources/bin/windows/lzma.dll +0 -0
- package/resources/bin/windows/pcre.dll +0 -0
- package/resources/bin/windows/pcreposix.dll +0 -0
- package/resources/bin/windows/plist.dll +0 -0
- package/resources/bin/windows/pthreadVC3.dll +0 -0
- package/resources/bin/windows/readline.dll +0 -0
- package/resources/bin/windows/usbmuxd.dll +0 -0
- package/resources/bin/windows/usbmuxd.exe +0 -0
- package/resources/bin/windows/vcruntime140.dll +0 -0
- package/resources/bin/windows/zip.dll +0 -0
- package/resources/bin/windows/zlib1.dll +0 -0
- package/resources/licenses/LGPL-2.1.txt +33 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
// src/logic/appleDeviceKit.ts
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
// src/utils/debug.ts
|
|
5
|
+
import createDebug from "debug";
|
|
6
|
+
var debug = createDebug("apple-kit");
|
|
7
|
+
var debugTask = createDebug("apple-kit:task");
|
|
8
|
+
function logInfo(message) {
|
|
9
|
+
debug(message);
|
|
10
|
+
}
|
|
11
|
+
function logTask(message) {
|
|
12
|
+
debugTask(message);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/utils/exec.ts
|
|
16
|
+
import { exec as execCallback } from "node:child_process";
|
|
17
|
+
import { promisify } from "node:util";
|
|
18
|
+
|
|
19
|
+
// src/utils/idevicePath.ts
|
|
20
|
+
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
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/logic/appleDeviceKit.ts
|
|
82
|
+
function parsePlistOutput(output) {
|
|
83
|
+
const result = {};
|
|
84
|
+
const lines = output.split("\n");
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
const colonIndex = line.indexOf(":");
|
|
87
|
+
if (colonIndex > 0) {
|
|
88
|
+
const key = line.substring(0, colonIndex).trim();
|
|
89
|
+
const value = line.substring(colonIndex + 1).trim();
|
|
90
|
+
result[key] = value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
function parseDeviceList(output) {
|
|
96
|
+
const devices = [];
|
|
97
|
+
const lines = output.trim().split("\n");
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
const trimmed = line.trim();
|
|
100
|
+
if (trimmed) {
|
|
101
|
+
const match = trimmed.match(/^([A-Fa-f0-9-]+)(?:\s+\((\w+)\))?/);
|
|
102
|
+
if (match) {
|
|
103
|
+
devices.push({
|
|
104
|
+
udid: match[1],
|
|
105
|
+
connectionType: match[2] === "Network" ? 2 : 1
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return devices;
|
|
111
|
+
}
|
|
112
|
+
function parseAppList(output) {
|
|
113
|
+
const apps = [];
|
|
114
|
+
const lines = output.trim().split("\n");
|
|
115
|
+
for (const line of lines) {
|
|
116
|
+
const match = line.match(/^([^,]+),\s*([^,]+),\s*"?([^"]+)"?/);
|
|
117
|
+
if (match) {
|
|
118
|
+
apps.push({
|
|
119
|
+
bundleId: match[1].trim(),
|
|
120
|
+
version: match[2].trim(),
|
|
121
|
+
displayName: match[3].trim(),
|
|
122
|
+
bundleVersion: ""
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return apps;
|
|
127
|
+
}
|
|
128
|
+
var AppleDeviceKit = class {
|
|
129
|
+
constructor(udid, port) {
|
|
130
|
+
this.port = port;
|
|
131
|
+
this.deviceId = udid;
|
|
132
|
+
logInfo(`AppleDeviceKit initialized for device: ${this.deviceId}`);
|
|
133
|
+
}
|
|
134
|
+
deviceId;
|
|
135
|
+
// ==================== Device Info ====================
|
|
136
|
+
/**
|
|
137
|
+
* Get detailed device information
|
|
138
|
+
*/
|
|
139
|
+
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
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// ==================== Trust/Pairing ====================
|
|
168
|
+
/**
|
|
169
|
+
* Check if device is paired/trusted with this computer
|
|
170
|
+
*/
|
|
171
|
+
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
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Wait for device to be paired
|
|
186
|
+
* Polls the pairing status until successful or timeout
|
|
187
|
+
*
|
|
188
|
+
* @param timeout Timeout in milliseconds (default: 120000)
|
|
189
|
+
* @param pollInterval Poll interval in milliseconds (default: 1000)
|
|
190
|
+
*/
|
|
191
|
+
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`);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Attempt to pair/trust the device
|
|
205
|
+
* User must accept the trust dialog on the device
|
|
206
|
+
*/
|
|
207
|
+
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
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Unpair/untrust the device
|
|
226
|
+
*/
|
|
227
|
+
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
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// ==================== App Installation ====================
|
|
241
|
+
/**
|
|
242
|
+
* Install an IPA file on the device (install agent)
|
|
243
|
+
*
|
|
244
|
+
* @param ipaPath Path to the IPA file
|
|
245
|
+
*/
|
|
246
|
+
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]);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Uninstall an app by bundle ID (uninstall agent)
|
|
259
|
+
*
|
|
260
|
+
* @param bundleId Application bundle identifier
|
|
261
|
+
*/
|
|
262
|
+
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]);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Check if an app is installed on the device
|
|
275
|
+
*
|
|
276
|
+
* @param bundleId Application bundle identifier
|
|
277
|
+
*/
|
|
278
|
+
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);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* List all installed user applications
|
|
285
|
+
*/
|
|
286
|
+
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
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// ==================== Launch Application ====================
|
|
303
|
+
/**
|
|
304
|
+
* 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
|
|
308
|
+
*/
|
|
309
|
+
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
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Start port forwarding and wait for it to be ready
|
|
364
|
+
*
|
|
365
|
+
* @param localPort Local port to listen on
|
|
366
|
+
* @param devicePort Device port to forward to
|
|
367
|
+
* @param _timeout Timeout in milliseconds (reserved for future use)
|
|
368
|
+
*/
|
|
369
|
+
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;
|
|
376
|
+
}
|
|
377
|
+
// ==================== Activation ====================
|
|
378
|
+
/**
|
|
379
|
+
* Get the activation state of the device
|
|
380
|
+
*/
|
|
381
|
+
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
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Activate the device
|
|
403
|
+
* Note: This requires a valid activation record or Apple server access
|
|
404
|
+
*/
|
|
405
|
+
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
|
+
}
|
|
418
|
+
}
|
|
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
|
+
/**
|
|
444
|
+
* Get the device UDID
|
|
445
|
+
*/
|
|
446
|
+
getDeviceId() {
|
|
447
|
+
return this.deviceId;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Get the logical port number
|
|
451
|
+
*/
|
|
452
|
+
getPort() {
|
|
453
|
+
return this.port;
|
|
454
|
+
}
|
|
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
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/logic/devicesMonitor.ts
|
|
474
|
+
import EventEmitter from "node:events";
|
|
475
|
+
import usbDeviceListener from "@mcesystems/usb-device-listener";
|
|
476
|
+
var APPLE_VID = 1452;
|
|
477
|
+
var DevicesMonitor = class {
|
|
478
|
+
constructor(config = {}, identifyAlreadyConnected = false) {
|
|
479
|
+
this.config = config;
|
|
480
|
+
this.identifyAlreadyConnected = identifyAlreadyConnected;
|
|
481
|
+
}
|
|
482
|
+
kits = /* @__PURE__ */ new Map();
|
|
483
|
+
eventEmitter;
|
|
484
|
+
/**
|
|
485
|
+
* Start tracking iOS device connections
|
|
486
|
+
*
|
|
487
|
+
* @returns EventEmitter that emits:
|
|
488
|
+
* - 'added': (deviceKit: AppleDeviceKit) when a device is connected
|
|
489
|
+
* - 'removed': (deviceId: string, port: number) when a device is disconnected
|
|
490
|
+
*/
|
|
491
|
+
async startTracking() {
|
|
492
|
+
logInfo("Starting iOS devices monitor");
|
|
493
|
+
const listenerConfig = {
|
|
494
|
+
logicalPortMap: this.config.logicalPortMap,
|
|
495
|
+
// Filter for Apple devices only
|
|
496
|
+
listenOnlyDevices: [
|
|
497
|
+
{ vid: APPLE_VID.toString(16).toUpperCase().padStart(4, "0"), pid: "" }
|
|
498
|
+
]
|
|
499
|
+
};
|
|
500
|
+
usbDeviceListener.startListening(listenerConfig);
|
|
501
|
+
this.eventEmitter = new EventEmitter();
|
|
502
|
+
if (this.identifyAlreadyConnected || this.config.identifyAlreadyConnected) {
|
|
503
|
+
await this.identifyConnectedDevices();
|
|
504
|
+
}
|
|
505
|
+
usbDeviceListener.onDeviceAdd((device) => {
|
|
506
|
+
if (device.vid !== APPLE_VID) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
logInfo(`Apple device connected: ${device.deviceId}`);
|
|
510
|
+
this.findAndEmitDevice(device);
|
|
511
|
+
});
|
|
512
|
+
usbDeviceListener.onDeviceRemove((device) => {
|
|
513
|
+
if (device.vid !== APPLE_VID) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
logInfo(`Apple device disconnected: ${device.deviceId}`);
|
|
517
|
+
for (const [udid, kit] of this.kits.entries()) {
|
|
518
|
+
if (kit.getPort() === device.logicalPort) {
|
|
519
|
+
this.kits.delete(udid);
|
|
520
|
+
this.eventEmitter?.emit("removed", udid, device.logicalPort);
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
return this.eventEmitter;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Stop tracking device connections
|
|
529
|
+
*/
|
|
530
|
+
async stopTracking() {
|
|
531
|
+
if (!this.eventEmitter) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
logInfo("Stopping iOS devices monitor");
|
|
535
|
+
this.eventEmitter.removeAllListeners();
|
|
536
|
+
this.kits.clear();
|
|
537
|
+
this.eventEmitter = void 0;
|
|
538
|
+
usbDeviceListener.stopListening();
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Get all currently tracked device kits
|
|
542
|
+
*/
|
|
543
|
+
getKits() {
|
|
544
|
+
return new Map(this.kits);
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Get a specific device kit by UDID
|
|
548
|
+
*/
|
|
549
|
+
getKit(udid) {
|
|
550
|
+
return this.kits.get(udid);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Find connected iOS device and emit event
|
|
554
|
+
*/
|
|
555
|
+
async findAndEmitDevice(usbDevice) {
|
|
556
|
+
const iOSDevices = await AppleDeviceKit.listDevices();
|
|
557
|
+
if (iOSDevices.length === 0) {
|
|
558
|
+
setTimeout(() => this.findAndEmitDevice(usbDevice), 500);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
for (const iDevice of iOSDevices) {
|
|
562
|
+
if (this.kits.has(iDevice.udid)) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const port = usbDevice.logicalPort ?? 0;
|
|
566
|
+
const kit = new AppleDeviceKit(iDevice.udid, port);
|
|
567
|
+
this.kits.set(iDevice.udid, kit);
|
|
568
|
+
setTimeout(() => {
|
|
569
|
+
this.eventEmitter?.emit("added", kit);
|
|
570
|
+
}, 0);
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Identify already connected iOS devices
|
|
576
|
+
*/
|
|
577
|
+
async identifyConnectedDevices() {
|
|
578
|
+
logInfo("Identifying already connected iOS devices");
|
|
579
|
+
const iOSDevices = await AppleDeviceKit.listDevices();
|
|
580
|
+
const usbDevices = usbDeviceListener.listDevices().filter((d) => d.vid === APPLE_VID);
|
|
581
|
+
for (let i = 0; i < iOSDevices.length; i++) {
|
|
582
|
+
const iDevice = iOSDevices[i];
|
|
583
|
+
const usbDevice = usbDevices[i];
|
|
584
|
+
if (this.kits.has(iDevice.udid)) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const port = usbDevice?.logicalPort ?? 0;
|
|
588
|
+
const kit = new AppleDeviceKit(iDevice.udid, port);
|
|
589
|
+
this.kits.set(iDevice.udid, kit);
|
|
590
|
+
setTimeout(() => {
|
|
591
|
+
this.eventEmitter?.emit("added", kit);
|
|
592
|
+
}, 0);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
};
|
|
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
|
+
export {
|
|
673
|
+
AppleDeviceKit,
|
|
674
|
+
DevicesMonitor,
|
|
675
|
+
ensureUsbmuxd,
|
|
676
|
+
isUsbmuxdRunning,
|
|
677
|
+
startUsbmuxd,
|
|
678
|
+
stopUsbmuxd
|
|
679
|
+
};
|
|
680
|
+
//# sourceMappingURL=index.js.map
|