Skip to content

Commit

Permalink
Fixes #75: Replace libimobiledevice with pymobiledevice3 (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
zner0L authored Jun 6, 2023
1 parent 8341876 commit 83a5ac5
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 38 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ If you want to work with physical devices, [some setup may be necessary dependin

### Host dependencies for iOS

For iOS, you need [`libimobiledevice`](https://libimobiledevice.org/) and `ideviceinstaller`. The distribution packages are fine, if available. On Windows, you will additionally need the Apple Device Driver and the Apple Application Support service. You can get those by installing iTunes.
On Windows, you will need the Apple Device Driver and the Apple Application Support service. You can get those by installing iTunes.

## Supported targets

Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ An ID of a known permission on iOS.

#### Defined in

[ios.ts:393](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L393)
[ios.ts:386](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L386)

___

Expand Down Expand Up @@ -332,7 +332,7 @@ The IDs of known permissions on iOS.

#### Defined in

[ios.ts:376](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L376)
[ios.ts:369](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L369)

## Functions

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"dependencies": {
"@napi-rs/lzma": "^1.1.2",
"andromatic": "^1.0.0",
"autopy": "1.0.0",
"autopy": "1.1.0",
"cross-fetch": "^3.1.5",
"execa": "^6.1.0",
"file-type": "^18.3.0",
Expand Down
2 changes: 2 additions & 0 deletions scripts/common/python.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ export const venvOptions = {
name: 'appstraction',
pythonVersion: '~3.11',
requirements: [
{ name: 'pip', version: '~=23.1' },
{ name: 'frida-tools', version: '~=12.1' },
{ name: 'objection', version: '~=1.11' },
{ name: 'pymobiledevice3', version: '~=1.42' },
],
};
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export type PlatformApi<
* Wait until the device or emulator has been connected and has booted up completely.
*
* @param tries The number of times to check if the device is present and booted. On Android, one try times out
* after 7 seconds and the default number of tries is 20. On iOS, one try takes about 1 second and the default
* number of tries is 100.
* after 7 seconds and the default number of tries is 20. On iOS, one try times out after 10 seconds and the
* default number of tries is 20.
*/
waitForDevice: (tries?: number) => Promise<void>;
/**
Expand Down
49 changes: 21 additions & 28 deletions src/ios.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { getVenv } from 'autopy';
import { createHash } from 'crypto';
import { execa } from 'execa';
import frida from 'frida';
import { NodeSSH } from 'node-ssh';
import type { PlatformApi, PlatformApiOptions, Proxy, SupportedCapability, SupportedRunTarget } from '.';
Expand Down Expand Up @@ -148,18 +147,28 @@ export const iosApi = <RunTarget extends SupportedRunTarget<'ios'>>(
},

resetDevice: asyncUnimplemented('resetDevice') as never,
async waitForDevice(tries = 100) {
async waitForDevice(tries = 20) {
if (
!(await retryCondition(
async () => (await execa('ideviceinfo', ['-k', 'DeviceName'], { reject: false })).exitCode === 0,
// Actually wait until the SpringBoard has been started and users could interact with the device.
() =>
python('pymobiledevice3', ['springboard', 'state', 'get'], {
reject: false,
timeout: 1000,
}).then(({ stderr, exitCode }) => exitCode === 0 && !stderr.includes('ERROR')),
tries
))
)
throw new Error('Failed to wait for device: No booted device found after timeout.');
},
async ensureDevice() {
if ((await execa('ideviceinfo', ['-k', 'DeviceName'], { reject: false })).exitCode !== 0)
throw new Error('You need to connect your device and trust this computer.');
const { exitCode, stdout: devices } = await python('pymobiledevice3', ['usbmux', 'list', '--no-color'], {
reject: false,
});
if (exitCode !== 0 && JSON.parse(devices).length !== 1)
throw new Error('You need to connect exactly one device. Multiple devices are not supported.');
if ((await python('pymobiledevice3', ['lockdown', 'info'], { reject: false })).exitCode !== 0)
throw new Error('You need to trust this computer on your device.');

if (options.capabilities.includes('frida')) {
const session = await frida
Expand All @@ -182,29 +191,13 @@ export const iosApi = <RunTarget extends SupportedRunTarget<'ios'>>(
},
clearStuckModals: asyncUnimplemented('clearStuckModals') as never,

isAppInstalled: async (appId) => {
const { stdout } =
process.platform === 'win32'
? await execa('ideviceinstaller', ['-l', '-o', 'list_all'])
: await execa('ideviceinstaller', ['list', '-o', 'list_all']);
return (
stdout
.split('\n')
// The first line is the header.
.slice(1)
.some((l) => l.startsWith(`${appId},`))
);
},
// We're using `libimobiledevice` instead of `cfgutil` because the latter doesn't wait for the app to be fully
// installed before exiting.
installApp: async (ipaPath) => {
if (process.platform === 'win32') await execa('ideviceinstaller', ['install', ipaPath]);
else await execa('ideviceinstaller', ['--install', ipaPath]);
},
uninstallApp: async (appId) => {
if (process.platform === 'win32') await execa('ideviceinstaller', ['uninstall', appId]);
else await execa('ideviceinstaller', ['--uninstall', appId]);
},
isAppInstalled: (appId) =>
python('pymobiledevice3', ['apps', 'list', '--user', '--no-color']).then(({ stdout }) =>
Object.keys(JSON.parse(stdout)).includes(appId)
),
installApp: (ipaPath) => python('pymobiledevice3', ['apps', 'install', ipaPath]).then(),
uninstallApp: (appId) => python('pymobiledevice3', ['apps', 'uninstall', appId]).then(),

async setAppPermissions(appId, _permissions) {
if (!options.capabilities.includes('ssh') || !options.capabilities.includes('frida'))
throw new Error('SSH and Frida are required for setting app permissions.');
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1653,10 +1653,10 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==

autopy@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/autopy/-/autopy-1.0.0.tgz#1e6f306aaebd49aa140c9fa6adcc55856cb029ae"
integrity sha512-fIP8OLX9eNkXc1olZULTY206EPH3ed7NJLq+Ws+reoH1bPRMKf//Lhyd+Oc3gPt6z7p3A0f3emy5LKysqjorAg==
autopy@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/autopy/-/autopy-1.1.0.tgz#273a01aa566550018ba8d854b79f4d9db7842cac"
integrity sha512-W8WhfWexE4B1ezbK/lwu5zA2qWmXIVizlLdMNb9pEcxZowN/aOfTa7XKZsGX7fV1YkP59heCF+Pty/uhx/QUaQ==
dependencies:
"@mongodb-js/zstd" "^1.1.0"
"@renovatebot/pep440" "^2.1.15"
Expand Down

0 comments on commit 83a5ac5

Please sign in to comment.