# @mcesystems/apple-kit

iOS device management toolkit using libimobiledevice and go-ios command-line tools. We use both because they each provide capabilities that the other does not. The package provides app installation/uninstallation, port forwarding, activation, and device property access.

## Features

- **App Management**: Install and uninstall iOS applications (.ipa files)
- **Port Forwarding**: Forward local ports to device ports (for debugging, etc.)
- **Device Activation**: Activate devices and skip setup steps
- **Trust/Pairing**: Handle device trust and pairing
- **Device Info**: Access device properties (name, model, iOS version, UDID, etc.)
- **Profile Management**: List and remove configuration profiles
- **Device Wipe**: Erase device data (factory reset)
- **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
- libimobiledevice tools (idevice\*)
- go-ios `ios` CLI binary

### Platform-specific Requirements

#### Windows
- libimobiledevice binaries - use the export script (see below)
- go-ios `ios.exe` (see Resources section below)
- iTunes installed OR Apple Mobile Device Support

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

Use the export script to bundle libimobiledevice 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`

### Resources and Binary Resolution

Set a resources directory once so both toolchains can be located:

```typescript
import path from "node:path";
import { AppleDeviceKit } from "@mcesystems/apple-kit";

AppleDeviceKit.setResourcesDir(path.join(process.cwd(), "resources"));
```

If you do not set a resources directory, make sure both toolchains are on your PATH.

The package looks for tools in this order:
1. `resourcesDir/ios/bin/{platform}/` (go-ios `ios` and libimobiledevice tools)
2. Homebrew paths on macOS (`/opt/homebrew/bin`, `/usr/local/bin`)
3. System PATH (for global installations)

## Usage

### List Devices (go-ios)

```typescript
import { createIosCli } from "@mcesystems/apple-kit";

// Example: <resourcesDir>/ios/bin/{platform}/ios(.exe)
const cli = createIosCli("path/to/ios");
const list = await cli.listDevices();
console.log(list.udids);
```

### Install/Uninstall Agent

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

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

// Install app locally, then (if MDM is configured) take over management
await device.installApp('/path/to/agent.ipa', {
  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.info();
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.UniqueDeviceID}`);
```

### Trust/Pairing

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

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

// Trust the device (initiates pairing and waits for user acceptance)
await device.trustDevice(60000);

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

### Port Forwarding

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

// Forward device port 8080 to a local port (auto-allocated)
const forward = await device.startPortForwardAsync(8080);
console.log(`Local port: ${forward.localPort}`);

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

// Stop forwarding when done
device.closePortForward();
```

### 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 (uses go-ios and MDM client)
const cleanup = await device.activate();
if (cleanup) {
  await cleanup(); // removes WiFi profile when done
}
```

The activation flow can install a WiFi profile based on environment variables:
`WIFI_SSID`, `WIFI_PASSWORD`, `WIFI_ENCRYPTION`, `WIFI_HIDDEN`, `WIFI_ENTERPRISE`,
`WIFI_USERNAME`, and `WIFI_EAP_TYPE`.

### Profiles

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

// List profiles
const profiles = await device.listProfiles();
console.log(profiles.profiles);

// Remove a profile by identifier
await device.removeProfile("com.example.profile");
```

### Wipe Device

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

// WARNING: this erases all data
await device.wipe();
```

## API Reference

### AppleDeviceKit

**Static methods:**
- `setResourcesDir(dir)`: Configure resources location

**Device Info:**
- `info()`: Get device properties
- `getDeviceId()`: Get the device UDID
- `getLogicalPort()`: Get the logical port number
- `getDevicePort()`: Get current local forwarded port (or null)

**App Management:**
- `installApp(ipaPath, options)`: Install IPA and take over via MDM if configured
- `uninstallApp(bundleId)`: Uninstall an app by bundle ID
- `isAppInstalled(bundleId)`: Check if app is installed
- `listApps()`: List all installed user apps

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

**Port Forwarding:**
- `startPortForwardAsync(devicePort, startupTimeout?)`: Start and wait for ready
- `closePortForward()`: Stop forwarding

**Profiles:**
- `listProfiles()`: List installed profiles
- `removeProfile(identifier)`: Remove profile by identifier

**Activation:**
- `getActivationState()`: Get activation state
- `activate()`: Activate the device (returns cleanup function)

**Device Wipe:**
- `wipe()`: Erase device data

**Lifecycle:**
- `dispose()`: Clean up resources and port forwards

### Types

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

## License

ISC

libimobiledevice is licensed under LGPL-2.1.
