# @mcesystems/apple-kit

iOS device management toolkit using libimobiledevice command-line tools. Provides device detection, app installation/uninstallation, port forwarding, activation, and device property access.

## Features

- **Device Detection**: Monitor iOS device connections via USB plug-and-play
- **App Management**: Install and uninstall iOS applications (.ipa files)
- **Launch Apps**: Start applications on the device
- **Port Forwarding**: Forward local ports to device ports (for debugging, etc.)
- **Device Activation**: Check and manage device activation state
- **Trust/Pairing**: Handle device trust and pairing
- **Device Info**: Access device properties (name, model, iOS version, UDID, etc.)
- **Cross-platform**: Works on Windows, macOS, and Linux

## Installation

```bash
npm install @mcesystems/apple-kit
```

## Requirements

- Node.js 18+
- iOS device connected via USB
- Device must be trusted/paired with the computer

### Platform-specific Requirements

#### Windows
- libimobiledevice binaries - use the export script (see below)
- iTunes installed OR Apple Mobile Device Support

#### macOS
**Option 1: Use bundled binaries (recommended for distribution)**

Use the export script to bundle binaries for your application:
```bash
# Using npx (after installing the package)
npx export-apple-resources /path/to/your-app/resources/apple-kit

# Or run the script directly
npx tsx node_modules/@mcesystems/apple-kit/scripts/export-resources.ts /path/to/your-app/resources
```

See [scripts/README.md](./scripts/README.md) for detailed prerequisites and instructions.

**Option 2: Use Homebrew installation (for development)**
- Install via Homebrew: `brew install libimobiledevice ideviceinstaller`
- Tools are auto-detected from `/opt/homebrew/bin` (Apple Silicon) or `/usr/local/bin` (Intel)

#### Linux
- libimobiledevice-utils: `sudo apt install libimobiledevice-utils`
- Tools are auto-detected from `/usr/bin` or `/usr/local/bin`

### Binary Resolution Order

The package looks for idevice tools in this order:
1. `IDeviceBinPath` environment variable (for custom paths)
2. Bundled resources in your app's `resources/bin/{platform}/` directory
3. Homebrew paths on macOS (`/opt/homebrew/bin`, `/usr/local/bin`)
4. System PATH (for global installations)

## Usage

### Device Monitoring

```typescript
import { DevicesMonitor } from '@mcesystems/apple-kit';

const monitor = new DevicesMonitor({
  logicalPortMap: {
    "Port_#0005.Hub_#0002": 1,
    "Port_#0006.Hub_#0002": 2
  }
});

const events = await monitor.startTracking();

events.on('added', async (deviceKit) => {
  console.log(`iOS device connected: ${deviceKit.getDeviceId()}`);
  const info = await deviceKit.getDeviceInfo();
  console.log(`  ${info.deviceName} - iOS ${info.productVersion}`);
});

events.on('removed', (deviceId, port) => {
  console.log(`iOS device disconnected: ${deviceId}`);
});

// Later: stop monitoring
await monitor.stopTracking();
```

### Install/Uninstall Agent

```typescript
import { AppleDeviceKit } from '@mcesystems/apple-kit';

const device = new AppleDeviceKit('device-udid', 1);

// Install an agent/app locally
await device.installApp('/path/to/agent.ipa');

// Install via MDM (ignores ipaPath, uses appId or url)
await device.installApp('/path/to/agent.ipa', {
  installViaMdm: true,
  mdm: {
    appId: 'com.example.agent',
    url: 'https://example.com/agent.ipa',
    waitForInstalled: true
  }
});

// Check if installed
const isInstalled = await device.isAppInstalled('com.example.agent');

// List all installed apps
const apps = await device.listApps();

// Uninstall an agent/app
await device.uninstallApp('com.example.agent');
```

### Device Info

```typescript
const device = new AppleDeviceKit('device-udid', 1);

const info = await device.getDeviceInfo();
console.log(`Device: ${info.deviceName}`);
console.log(`Model: ${info.productType}`);
console.log(`iOS: ${info.productVersion} (${info.buildVersion})`);
console.log(`Serial: ${info.serialNumber}`);
console.log(`UDID: ${info.udid}`);
```

### Trust/Pairing

```typescript
const device = new AppleDeviceKit('device-udid', 1);

// Check if device is trusted
const isPaired = await device.isPaired();

// Initiate pairing (user must accept on device)
await device.pair();

// Wait for user to accept trust dialog
await device.waitForPairing(60000); // 60 second timeout

// Unpair device
await device.unpair();
```

### Launch Application

```typescript
const device = new AppleDeviceKit('device-udid', 1);

// Launch an app
await device.launchApp('com.example.myapp');

// Launch with arguments
await device.launchApp('com.example.myapp', ['--debug', '--port=8080']);
```

### Port Forwarding

```typescript
const device = new AppleDeviceKit('device-udid', 1);

// Forward local port 8080 to device port 8080
const forward = device.startPortForward(8080, 8080);

// Use the forwarded connection...
// connect to localhost:8080 to reach device:8080

// Stop forwarding when done
forward.stop();

// Or use async version that waits for ready
const forwardAsync = await device.startPortForwardAsync(8080, 8080);
```

### Activation

```typescript
const device = new AppleDeviceKit('device-udid', 1);

// Get activation state
const state = await device.getActivationState();
console.log(`Activated: ${state.isActivated}`);
console.log(`State: ${state.activationState}`);

// Activate device (requires valid activation record)
await device.activate({
  resourcesDir: process.env.APPLE_KIT_RESOURCES_DIR,
});

// Deactivate device
await device.deactivate();
```

When `resourcesDir` is set, the activation flow reads MDM credentials from
`@clientLocal.json` inside that folder, plist templates from `resourcesDir/plist`,
and the iOS CLI binary from `resourcesDir/ios/bin/{platform}`.
If `resourcesDir/mdm/enrollment_{udid}.mobileconfig` or
`resourcesDir/mdm/enrollment.mobileconfig` exists, it is installed instead of
calling the MDM API (offline enrollment).

### Running Without iTunes (usbmuxd)

On Windows, iTunes provides the Apple Mobile Device Service for USB communication. If you don't have iTunes installed, you can run the bundled `usbmuxd`:

```typescript
import { startUsbmuxd, stopUsbmuxd, ensureUsbmuxd } from '@mcesystems/apple-kit';

// Start usbmuxd daemon (required if iTunes is not installed)
const daemon = startUsbmuxd();

// Or ensure it's running (starts if not already running)
ensureUsbmuxd();

// Now you can use device operations...
const devices = await AppleDeviceKit.listDevices();

// When done, stop the daemon
stopUsbmuxd();
// or
daemon.stop();
```

**Note:** The usbmuxd daemon must be running before connecting devices. Start it before plugging in your iOS device.

## API Reference

### AppleDeviceKit

**Static methods:**
- `listDevices()`: Get all connected iOS devices

**Device Info:**
- `getDeviceInfo()`: Get device properties
- `getDeviceId()`: Get the device UDID
- `getPort()`: Get the logical port number

**App Management:**
- `installApp(ipaPath, options?)`: Install an IPA file or via MDM
- `uninstallApp(bundleId)`: Uninstall an app by bundle ID
- `isAppInstalled(bundleId)`: Check if app is installed
- `listApps()`: List all installed user apps
- `launchApp(bundleId, args?)`: Launch an application

**Trust/Pairing:**
- `isPaired()`: Check if device is paired/trusted
- `pair()`: Initiate pairing (user must accept on device)
- `unpair()`: Remove pairing/trust
- `waitForPairing(timeout?)`: Wait for device to be paired

**Port Forwarding:**
- `startPortForward(localPort, devicePort)`: Start port forwarding
- `startPortForwardAsync(localPort, devicePort)`: Start and wait for ready

**Activation:**
- `getActivationState()`: Get activation state
- `activate()`: Activate the device
- `deactivate()`: Deactivate the device

### DevicesMonitor

- `startTracking()`: Start monitoring for iOS device connections
- `stopTracking()`: Stop monitoring
- `getKits()`: Get all currently tracked devices
- `getKit(udid)`: Get a specific device kit by UDID

### usbmuxd Functions

- `startUsbmuxd(foreground?)`: Start the usbmuxd daemon
- `stopUsbmuxd()`: Stop the daemon
- `isUsbmuxdRunning()`: Check if daemon is running
- `ensureUsbmuxd()`: Start if not already running

To use a custom resources folder, pass it as the second parameter:
`startUsbmuxd(false, { resourcesDir: "..." })`.

### Types

```typescript
interface PortForwardHandle {
  stop: () => void;
  localPort: number;
  devicePort: number;
  process: ChildProcess;
}

interface ActivationState {
  isActivated: boolean;
  activationState: string;
}
```

## License

ISC

libimobiledevice is licensed under LGPL-2.1.
