Index

UDITA: Controlling iPhones Without Jailbreaking Them

I built UDITA so you can control an iPhone from your Mac: tap, swipe, launch apps, run automation, all through a dashboard and API. Under the hood it's WebDriverAgent plus a Python bridge and a single CLI. Getting there meant running into a lot of real-world issues. This is a straight run-through of those issues and how I solved them.


Code Signing

First-time setup required building WebDriverAgent with Xcode and signing it with your Apple Developer team. People kept hitting "No Account for Team" or "Signing for WebDriverAgentRunner requires a development team." The project had manual signing and hardcoded team/provisioning references. If you didn't already know where to find your Team ID or how to set up signing in Xcode, you were stuck.

I built auto-detection that pulls your Team ID from multiple sources: Keychain certificates first (Apple Development / iPhone Developer / iPhone Distribution), then Xcode preferences, then a cached value so it only asks once. The setup configures automatic signing in the Xcode project directly — switches CODE_SIGN_STYLE and ProvisioningStyle to Automatic, injects DevelopmentTeam into the right target attributes, strips hardcoded provisioning profile specifiers so Xcode can manage everything.

If auto-detection fails, there's an interactive fallback with step-by-step instructions for both Keychain and Xcode. On "No Account for Team" errors it opens Xcode for sign-in. Build retries up to 3 attempts — on missing profiles or certificates it cleans DerivedData, tries unlocking the keychain, re-applies the signing fixes, and goes again.

Most users run ./udita setup once, confirm the auto-detected Team ID, and get a successful build without ever opening Xcode's signing UI.


Setup Hanging Forever

During setup I run xcodebuild build-for-testing for WebDriverAgent. Sometimes the build hung — waiting for input, network, or a stuck step — and the script never came back. No timeout, no visible output.

Fixed this by showing build output in real time, filtered for the lines that matter (errors, warnings, Signing, Compiling, Linking, BUILD SUCCEEDED/FAILED). Added a 5-minute timeout — background the build, poll, kill if it exceeds the limit with a clear message. After any failure I analyze wda_build.log for known strings and either trigger the right fix automatically or print targeted instructions.

Setup either finishes or fails in a bounded time. You always know what happened.


WDA Connection Failures

After WDA was built and launched, the bridge had to talk to it at host:8100. Port 8100 might already be in use from a previous run. USB needs a relay (tidevice relay or iproxy); Wi-Fi needs the device IP. Failures were opaque — "can't connect" without saying whether it was port conflict, relay not running, or wrong IP.

I added a close_port helper that frees 8100 and 5050 before starting (TERM, wait, KILL if needed). The relay only starts when the device is confirmed ready via tidevice info. After a successful connection I cache the host/connection type/timestamp in .wda_host — next start tries the cached IP first, skips it if older than an hour. Connection order: cached IP, then USB, then Wi-Fi scan.

WDA verification retries up to 15 attempts with a spinner, hitting the /status endpoint. When it fails, a diagnostic checklist runs: is WebDriverAgent running? Is port 8100 open and by which process? Is the USB relay running? Is the iPhone visible to tidevice? Then it prints the specific fix — "trust certificate" or "restart WDA" or "reconnect iPhone."


Dashboard Touch Gestures

On the web dashboard, hold and swipe gestures didn't match expectation. Holds were too short or not recognized. Swipes calculated from the wrong points or fired too easily on tiny movements.

Hold now sends touchAndHold with a 500ms duration so it's clearly a long-press. Swipe uses the release point as the end of the gesture and enforces a 20px minimum distance. Rapid touch events are debounced so the bridge doesn't get flooded with duplicates.

Small changes, but the dashboard went from unreliable to predictable.


App Launch on iOS 16+

I used WDA's app launch/activate APIs to open apps by bundle ID. On iOS 16 and later, that could crash or fail in ways that looked like my bug. Turned out to be a known WDA/iOS issue around NSNull in the launch/activate path.

The fix: prefer URL schemes for known apps. I maintain a mapping from bundle ID to URL scheme — com.apple.mobilesafari maps to http://, com.burbn.instagram maps to instagram://, and so on for 80+ apps. For those, I go to homescreen first, then open the URL with the session's url command. That path avoids the broken launch/activate codepath entirely. For bundles not in the map, it falls back to the WDA activate API with known limitations.


One Script

Early on there were multiple entrypoints: install-wda.sh, start.sh, run.sh, and a long README with many steps. New users had to choose the right script and follow 10+ steps.

I collapsed everything into a single udita script at the repo root. No arguments runs an interactive menu (Start, Stop, Status, Setup). Also supports ./udita start, ./udita stop, ./udita status, ./udita setup. Setup does everything — dependencies, Team ID detection, signing configuration, unique bundle IDs, build with retries and timeout, and clear post-setup instructions.

Before: 11 steps, 30 minutes. After: one command, about 5 minutes.


Developer Mode and First-Run Trust

On iOS 16+, Apple requires Developer Mode to be enabled for running developer-signed apps like WDA. Even with signing fixed, users would build successfully and then WDA wouldn't start until they enabled Developer Mode and trusted the certificate. I didn't explain that up front.

After a successful build, setup now prints post-setup instructions: trust the developer certificate (Settings, General, VPN & Device Management), enable Developer Mode (Settings, Privacy & Security, Developer Mode) and restart for iOS 16+, keep the iPhone connected via USB and unlocked for the first run. If WDA exits during "start," it shows the last lines of wda.log and reminds about unlocking, trusting, and Developer Mode.


Quick Reference

IssueFix
Code signing / "No Account for Team"Auto-detect Team ID, set automatic signing, retry with targeted fixes
Setup hangingReal-time build output, 5-min timeout, log analysis
WDA connection failuresFree ports, start relay when device ready, IP cache, diagnostics
Dashboard hold/swipe500ms hold, swipe from release point, 20px threshold, debounce
App launch on iOS 16+URL schemes for 80+ known apps, fallback to activate
Fragmented setupSingle udita script, one-command setup
First-run trust / Developer ModePost-setup instructions and failure hints

UDITA is open source at github.com/photon-hq/UDITA. Shell + Python + HTML, WebDriverAgent under the hood, USB-first with Wi-Fi fallback.