pymobiledevice3 6.2.0__py3-none-any.whl → 7.0.0__py3-none-any.whl
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.
- pymobiledevice3/__main__.py +136 -44
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +19 -20
- pymobiledevice3/cli/activation.py +24 -22
- pymobiledevice3/cli/afc.py +49 -41
- pymobiledevice3/cli/amfi.py +13 -18
- pymobiledevice3/cli/apps.py +71 -65
- pymobiledevice3/cli/backup.py +134 -93
- pymobiledevice3/cli/bonjour.py +31 -29
- pymobiledevice3/cli/cli_common.py +179 -232
- pymobiledevice3/cli/companion_proxy.py +12 -12
- pymobiledevice3/cli/crash.py +95 -52
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +387 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +18 -22
- pymobiledevice3/cli/lockdown.py +70 -75
- pymobiledevice3/cli/mounter.py +99 -57
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +36 -20
- pymobiledevice3/cli/power_assertion.py +15 -16
- pymobiledevice3/cli/processes.py +11 -17
- pymobiledevice3/cli/profile.py +120 -75
- pymobiledevice3/cli/provision.py +27 -26
- pymobiledevice3/cli/remote.py +108 -99
- pymobiledevice3/cli/restore.py +134 -129
- pymobiledevice3/cli/springboard.py +50 -50
- pymobiledevice3/cli/syslog.py +138 -74
- pymobiledevice3/cli/usbmux.py +66 -27
- pymobiledevice3/cli/version.py +2 -5
- pymobiledevice3/cli/webinspector.py +149 -103
- pymobiledevice3/remote/remote_service_discovery.py +11 -10
- pymobiledevice3/restore/device.py +28 -4
- pymobiledevice3/service_connection.py +1 -1
- pymobiledevice3/services/mobilebackup2.py +4 -1
- pymobiledevice3/services/screenshot.py +2 -2
- pymobiledevice3/services/web_protocol/automation_session.py +4 -2
- pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
- pymobiledevice3/services/web_protocol/element.py +3 -3
- {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/METADATA +3 -2
- {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/RECORD +58 -45
- pymobiledevice3/cli/completions.py +0 -50
- pymobiledevice3/cli/developer.py +0 -1645
- pymobiledevice3/cli/diagnostics.py +0 -110
- {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/WHEEL +0 -0
- {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import inspect
|
|
2
3
|
import logging
|
|
3
4
|
import re
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from asyncio import CancelledError
|
|
6
7
|
from collections.abc import AsyncIterator, Iterable
|
|
7
|
-
from contextlib import asynccontextmanager
|
|
8
|
+
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
|
8
9
|
from functools import update_wrapper
|
|
9
|
-
from
|
|
10
|
+
from string import Template
|
|
11
|
+
from typing import Annotated, Any, Optional
|
|
10
12
|
|
|
11
|
-
import click
|
|
12
13
|
import inquirer3
|
|
13
14
|
import IPython
|
|
14
15
|
import nest_asyncio
|
|
16
|
+
import typer
|
|
15
17
|
import uvicorn
|
|
16
18
|
from inquirer3.themes import GreenPassion
|
|
17
19
|
from prompt_toolkit import HTML, PromptSession
|
|
18
20
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
19
|
-
from prompt_toolkit.completion.base import CompleteEvent, Completer, Completion
|
|
21
|
+
from prompt_toolkit.completion.base import CompleteEvent, Completer, Completion
|
|
22
|
+
from prompt_toolkit.document import Document
|
|
20
23
|
from prompt_toolkit.history import FileHistory
|
|
21
24
|
from prompt_toolkit.lexers import PygmentsLexer
|
|
22
25
|
from prompt_toolkit.patch_stdout import patch_stdout
|
|
23
26
|
from prompt_toolkit.styles import style_from_pygments_cls
|
|
24
27
|
from pygments import formatters, highlight, lexers
|
|
25
28
|
from pygments.styles import get_style_by_name
|
|
29
|
+
from typer_injector import InjectingTyper
|
|
26
30
|
|
|
27
|
-
from pymobiledevice3.cli.cli_common import
|
|
31
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep
|
|
28
32
|
from pymobiledevice3.common import get_home_folder
|
|
29
33
|
from pymobiledevice3.exceptions import (
|
|
30
34
|
InspectorEvaluateError,
|
|
@@ -33,35 +37,34 @@ from pymobiledevice3.exceptions import (
|
|
|
33
37
|
WebInspectorNotEnabledError,
|
|
34
38
|
WirError,
|
|
35
39
|
)
|
|
36
|
-
from pymobiledevice3.lockdown import
|
|
40
|
+
from pymobiledevice3.lockdown import create_using_usbmux
|
|
37
41
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
38
42
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
39
43
|
from pymobiledevice3.services.web_protocol.cdp_server import app
|
|
40
44
|
from pymobiledevice3.services.web_protocol.driver import By, Cookie, WebDriver
|
|
41
45
|
from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
|
|
42
|
-
from pymobiledevice3.services.webinspector import SAFARI, ApplicationPage, WebinspectorService
|
|
43
|
-
|
|
44
|
-
SCRIPT = """
|
|
45
|
-
function inspectedPage_evalResult_getCompletions(primitiveType) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
for(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}}
|
|
46
|
+
from pymobiledevice3.services.webinspector import SAFARI, Application, ApplicationPage, WebinspectorService
|
|
47
|
+
|
|
48
|
+
SCRIPT = Template("""
|
|
49
|
+
function inspectedPage_evalResult_getCompletions(primitiveType) {
|
|
50
|
+
let resultSet = {};
|
|
51
|
+
let object = primitiveType;
|
|
52
|
+
for (let o = object; o; o = o.__proto__) {
|
|
53
|
+
try {
|
|
54
|
+
let names = Object.getOwnPropertyNames(o);
|
|
55
|
+
for (let i = 0; i < names.length; ++i)
|
|
56
|
+
resultSet[names[i]] = true;
|
|
57
|
+
} catch(e) {}
|
|
58
|
+
}
|
|
56
59
|
return resultSet;
|
|
57
|
-
}
|
|
60
|
+
}
|
|
58
61
|
|
|
59
|
-
try {
|
|
60
|
-
inspectedPage_evalResult_getCompletions({object})
|
|
61
|
-
}
|
|
62
|
-
"""
|
|
62
|
+
try {
|
|
63
|
+
inspectedPage_evalResult_getCompletions(${object})
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
""")
|
|
63
66
|
|
|
64
|
-
JS_RESERVED_WORDS =
|
|
67
|
+
JS_RESERVED_WORDS = frozenset({
|
|
65
68
|
"abstract",
|
|
66
69
|
"arguments",
|
|
67
70
|
"await",
|
|
@@ -126,51 +129,67 @@ JS_RESERVED_WORDS = [
|
|
|
126
129
|
"while",
|
|
127
130
|
"with",
|
|
128
131
|
"yield",
|
|
129
|
-
|
|
132
|
+
})
|
|
130
133
|
|
|
131
134
|
OSUTILS = get_os_utils()
|
|
132
135
|
logger = logging.getLogger(__name__)
|
|
133
136
|
|
|
134
137
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
cli = InjectingTyper(
|
|
139
|
+
name="webinspector",
|
|
140
|
+
help=(
|
|
141
|
+
"Control Safari/WebViews (tabs, automation, JS shells, CDP). "
|
|
142
|
+
"Requires Web Inspector and Remote Automation enabled on the device."
|
|
143
|
+
),
|
|
144
|
+
no_args_is_help=True,
|
|
145
|
+
)
|
|
138
146
|
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
def catch_errors(func):
|
|
149
|
+
errors = {
|
|
150
|
+
LaunchingApplicationError: "Unable to launch application (try to unlock device)",
|
|
151
|
+
WebInspectorNotEnabledError: "Web inspector is not enabled",
|
|
152
|
+
RemoteAutomationNotEnabledError: "Remote automation is not enabled",
|
|
153
|
+
}
|
|
144
154
|
|
|
155
|
+
def handle_error(e):
|
|
156
|
+
logger.error(next(msg for exc, msg in errors.items() if isinstance(e, exc)))
|
|
145
157
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
if inspect.iscoroutinefunction(func):
|
|
159
|
+
|
|
160
|
+
async def catch_function(*args, **kwargs):
|
|
161
|
+
try:
|
|
162
|
+
return await func(*args, **kwargs)
|
|
163
|
+
except tuple(errors) as e:
|
|
164
|
+
handle_error(e)
|
|
165
|
+
|
|
166
|
+
else:
|
|
167
|
+
|
|
168
|
+
def catch_function(*args, **kwargs):
|
|
169
|
+
try:
|
|
170
|
+
return func(*args, **kwargs)
|
|
171
|
+
except tuple(errors) as e:
|
|
172
|
+
handle_error(e)
|
|
156
173
|
|
|
157
174
|
return update_wrapper(catch_function, func)
|
|
158
175
|
|
|
159
176
|
|
|
160
|
-
def reload_pages(inspector: WebinspectorService):
|
|
161
|
-
inspector.get_open_pages()
|
|
177
|
+
async def reload_pages(inspector: WebinspectorService) -> None:
|
|
178
|
+
await inspector.get_open_pages()
|
|
162
179
|
# Best effort.
|
|
163
|
-
inspector.flush_input(2)
|
|
180
|
+
await inspector.flush_input(2)
|
|
164
181
|
|
|
165
182
|
|
|
166
|
-
async def create_webinspector_and_launch_app(
|
|
183
|
+
async def create_webinspector_and_launch_app(
|
|
184
|
+
lockdown: LockdownServiceProvider, timeout: float, app: str
|
|
185
|
+
) -> tuple[WebinspectorService, Application]:
|
|
167
186
|
inspector = WebinspectorService(lockdown=lockdown)
|
|
168
187
|
await inspector.connect(timeout)
|
|
169
188
|
application = await inspector.open_app(app)
|
|
170
189
|
return inspector, application
|
|
171
190
|
|
|
172
191
|
|
|
173
|
-
async def opened_tabs_task(service_provider:
|
|
192
|
+
async def opened_tabs_task(service_provider: LockdownServiceProvider, timeout: float) -> None:
|
|
174
193
|
inspector = WebinspectorService(lockdown=service_provider)
|
|
175
194
|
await inspector.connect(timeout)
|
|
176
195
|
application_pages = await inspector.get_open_application_pages(timeout=timeout)
|
|
@@ -179,10 +198,15 @@ async def opened_tabs_task(service_provider: LockdownClient, timeout):
|
|
|
179
198
|
await inspector.close()
|
|
180
199
|
|
|
181
200
|
|
|
182
|
-
@
|
|
183
|
-
@click.option("-t", "--timeout", default=3, show_default=True, type=float)
|
|
201
|
+
@cli.command()
|
|
184
202
|
@catch_errors
|
|
185
|
-
def opened_tabs(
|
|
203
|
+
def opened_tabs(
|
|
204
|
+
service_provider: ServiceProviderDep,
|
|
205
|
+
timeout: Annotated[
|
|
206
|
+
float,
|
|
207
|
+
typer.Option("--timeout", "-t", help="Seconds to wait for WebInspector to respond."),
|
|
208
|
+
] = 3.0,
|
|
209
|
+
) -> None:
|
|
186
210
|
"""
|
|
187
211
|
Show all currently opened tabs.
|
|
188
212
|
|
|
@@ -196,7 +220,7 @@ def opened_tabs(service_provider: LockdownClient, timeout):
|
|
|
196
220
|
|
|
197
221
|
|
|
198
222
|
@catch_errors
|
|
199
|
-
async def launch_task(service_provider:
|
|
223
|
+
async def launch_task(service_provider: LockdownServiceProvider, url, timeout) -> None:
|
|
200
224
|
inspector, safari = await create_webinspector_and_launch_app(service_provider, timeout, SAFARI)
|
|
201
225
|
session = await inspector.automation_session(safari)
|
|
202
226
|
driver = WebDriver(session)
|
|
@@ -209,11 +233,16 @@ async def launch_task(service_provider: LockdownClient, url, timeout):
|
|
|
209
233
|
await inspector.close()
|
|
210
234
|
|
|
211
235
|
|
|
212
|
-
@
|
|
213
|
-
@click.argument("url")
|
|
214
|
-
@click.option("-t", "--timeout", default=3, show_default=True, type=float)
|
|
236
|
+
@cli.command()
|
|
215
237
|
@catch_errors
|
|
216
|
-
def launch(
|
|
238
|
+
def launch(
|
|
239
|
+
service_provider: ServiceProviderDep,
|
|
240
|
+
url: str,
|
|
241
|
+
timeout: Annotated[
|
|
242
|
+
float,
|
|
243
|
+
typer.Option("--timeout", "-t", help="Seconds to wait for WebInspector to respond."),
|
|
244
|
+
] = 3.0,
|
|
245
|
+
) -> None:
|
|
217
246
|
"""
|
|
218
247
|
Launch a specific URL in Safari.
|
|
219
248
|
|
|
@@ -252,7 +281,7 @@ driver.add_cookie(
|
|
|
252
281
|
|
|
253
282
|
|
|
254
283
|
@catch_errors
|
|
255
|
-
async def shell_task(service_provider:
|
|
284
|
+
async def shell_task(service_provider: LockdownServiceProvider, timeout: float) -> None:
|
|
256
285
|
inspector, safari = await create_webinspector_and_launch_app(service_provider, timeout, SAFARI)
|
|
257
286
|
session = await inspector.automation_session(safari)
|
|
258
287
|
driver = WebDriver(session)
|
|
@@ -271,9 +300,15 @@ async def shell_task(service_provider: LockdownClient, timeout):
|
|
|
271
300
|
await inspector.close()
|
|
272
301
|
|
|
273
302
|
|
|
274
|
-
@
|
|
275
|
-
@
|
|
276
|
-
def shell(
|
|
303
|
+
@cli.command()
|
|
304
|
+
@catch_errors
|
|
305
|
+
def shell(
|
|
306
|
+
service_provider: ServiceProviderDep,
|
|
307
|
+
timeout: Annotated[
|
|
308
|
+
float,
|
|
309
|
+
typer.Option("--timeout", "-t", help="Seconds to wait for WebInspector to respond."),
|
|
310
|
+
] = 3.0,
|
|
311
|
+
) -> None:
|
|
277
312
|
"""
|
|
278
313
|
Create an IPython shell for interacting with a WebView.
|
|
279
314
|
|
|
@@ -289,14 +324,23 @@ def shell(service_provider: LockdownClient, timeout):
|
|
|
289
324
|
asyncio.run(shell_task(service_provider, timeout), debug=True)
|
|
290
325
|
|
|
291
326
|
|
|
292
|
-
@
|
|
293
|
-
@click.option("-t", "--timeout", default=3, show_default=True, type=float)
|
|
294
|
-
@click.option("--automation", is_flag=True, help="Use remote automation")
|
|
295
|
-
@click.option("--no-open-safari", is_flag=True, help="Avoid opening the Safari app")
|
|
296
|
-
@click.argument("url", required=False, default="")
|
|
327
|
+
@cli.command()
|
|
297
328
|
@catch_errors
|
|
298
329
|
def js_shell(
|
|
299
|
-
service_provider:
|
|
330
|
+
service_provider: ServiceProviderDep,
|
|
331
|
+
url: str = "",
|
|
332
|
+
timeout: Annotated[
|
|
333
|
+
float,
|
|
334
|
+
typer.Option("--timeout", "-t", help="Seconds to wait for WebInspector to respond."),
|
|
335
|
+
] = 3.0,
|
|
336
|
+
automation: Annotated[
|
|
337
|
+
bool,
|
|
338
|
+
typer.Option(help="Use remote automation (requires Remote Automation toggle)."),
|
|
339
|
+
] = False,
|
|
340
|
+
open_safari: Annotated[
|
|
341
|
+
bool,
|
|
342
|
+
typer.Option(help="Use an existing WebView; skip auto-opening Safari."),
|
|
343
|
+
] = False,
|
|
300
344
|
) -> None:
|
|
301
345
|
"""
|
|
302
346
|
Create a javascript shell. This interpreter runs on your local machine,
|
|
@@ -313,7 +357,7 @@ def js_shell(
|
|
|
313
357
|
"""
|
|
314
358
|
|
|
315
359
|
js_shell_class = AutomationJsShell if automation else InspectorJsShell
|
|
316
|
-
asyncio.run(run_js_shell(js_shell_class, service_provider, timeout, url,
|
|
360
|
+
asyncio.run(run_js_shell(js_shell_class, service_provider, timeout, url, open_safari))
|
|
317
361
|
|
|
318
362
|
|
|
319
363
|
udid = ""
|
|
@@ -325,10 +369,8 @@ def create_app():
|
|
|
325
369
|
return app
|
|
326
370
|
|
|
327
371
|
|
|
328
|
-
@
|
|
329
|
-
|
|
330
|
-
@click.option("--port", type=click.INT, default=9222)
|
|
331
|
-
def cdp(service_provider: LockdownClient, host, port):
|
|
372
|
+
@cli.command()
|
|
373
|
+
def cdp(service_provider: ServiceProviderDep, host: str = "127.0.0.1", port: int = 9222) -> None:
|
|
332
374
|
"""
|
|
333
375
|
Start a CDP server for debugging WebViews.
|
|
334
376
|
|
|
@@ -339,7 +381,7 @@ def cdp(service_provider: LockdownClient, host, port):
|
|
|
339
381
|
global udid
|
|
340
382
|
udid = service_provider.udid
|
|
341
383
|
uvicorn.run(
|
|
342
|
-
"
|
|
384
|
+
f"{__name__}:{create_app.__name__}",
|
|
343
385
|
host=host,
|
|
344
386
|
port=port,
|
|
345
387
|
factory=True,
|
|
@@ -354,7 +396,7 @@ async def get_js_completions(jsshell: "JsShell", obj: str, prefix: str) -> Async
|
|
|
354
396
|
return
|
|
355
397
|
|
|
356
398
|
try:
|
|
357
|
-
for key in await jsshell.evaluate_expression(SCRIPT.
|
|
399
|
+
for key in await jsshell.evaluate_expression(SCRIPT.substitute(object=obj), return_by_value=True):
|
|
358
400
|
if not key.startswith(prefix):
|
|
359
401
|
continue
|
|
360
402
|
yield Completion(key.removeprefix(prefix), display=key)
|
|
@@ -364,14 +406,14 @@ async def get_js_completions(jsshell: "JsShell", obj: str, prefix: str) -> Async
|
|
|
364
406
|
|
|
365
407
|
|
|
366
408
|
class JsShellCompleter(Completer):
|
|
367
|
-
def __init__(self, jsshell: "JsShell"):
|
|
368
|
-
self.jsshell = jsshell
|
|
409
|
+
def __init__(self, jsshell: "JsShell") -> None:
|
|
410
|
+
self.jsshell: JsShell = jsshell
|
|
369
411
|
|
|
370
|
-
def get_completions_async(
|
|
412
|
+
async def get_completions_async(
|
|
371
413
|
self,
|
|
372
414
|
document: Document,
|
|
373
415
|
complete_event: CompleteEvent,
|
|
374
|
-
) ->
|
|
416
|
+
) -> AsyncIterator[Completion]:
|
|
375
417
|
# Build the JS expression we want to inspect
|
|
376
418
|
text = f"globalThis.{document.text_before_cursor}"
|
|
377
419
|
|
|
@@ -379,7 +421,7 @@ class JsShellCompleter(Completer):
|
|
|
379
421
|
matches = re.findall(r"[a-zA-Z_][a-zA-Z_0-9.]+", text)
|
|
380
422
|
if not matches:
|
|
381
423
|
# async *generator*: just end, don't return a list
|
|
382
|
-
return
|
|
424
|
+
return
|
|
383
425
|
|
|
384
426
|
text = matches[-1]
|
|
385
427
|
if "." in text:
|
|
@@ -389,7 +431,8 @@ class JsShellCompleter(Completer):
|
|
|
389
431
|
prefix = ""
|
|
390
432
|
|
|
391
433
|
# This should return an iterable of Completion (or something we can wrap)
|
|
392
|
-
|
|
434
|
+
async for completion in get_js_completions(self.jsshell, js_obj, prefix):
|
|
435
|
+
yield completion
|
|
393
436
|
|
|
394
437
|
# Optional: keep sync completions empty so PTK knows we prefer async
|
|
395
438
|
def get_completions(
|
|
@@ -403,7 +446,7 @@ class JsShellCompleter(Completer):
|
|
|
403
446
|
class JsShell(ABC):
|
|
404
447
|
def __init__(self) -> None:
|
|
405
448
|
super().__init__()
|
|
406
|
-
self.prompt_session = PromptSession(
|
|
449
|
+
self.prompt_session: PromptSession = PromptSession(
|
|
407
450
|
lexer=PygmentsLexer(lexers.JavascriptLexer),
|
|
408
451
|
auto_suggest=AutoSuggestFromHistory(),
|
|
409
452
|
style=style_from_pygments_cls(get_style_by_name("stata-dark")),
|
|
@@ -413,18 +456,17 @@ class JsShell(ABC):
|
|
|
413
456
|
|
|
414
457
|
@classmethod
|
|
415
458
|
@abstractmethod
|
|
416
|
-
def create(
|
|
417
|
-
|
|
459
|
+
def create(
|
|
460
|
+
cls, lockdown: LockdownServiceProvider, timeout: float, open_safari: bool
|
|
461
|
+
) -> "AbstractAsyncContextManager[JsShell]": ...
|
|
418
462
|
|
|
419
463
|
@abstractmethod
|
|
420
|
-
async def evaluate_expression(self, exp, return_by_value: bool = False):
|
|
421
|
-
pass
|
|
464
|
+
async def evaluate_expression(self, exp, return_by_value: bool = False) -> Any: ...
|
|
422
465
|
|
|
423
466
|
@abstractmethod
|
|
424
|
-
async def navigate(self, url: str):
|
|
425
|
-
pass
|
|
467
|
+
async def navigate(self, url: str) -> None: ...
|
|
426
468
|
|
|
427
|
-
async def js_iter(self):
|
|
469
|
+
async def js_iter(self) -> None:
|
|
428
470
|
with patch_stdout(True):
|
|
429
471
|
exp = await self.prompt_session.prompt_async(HTML('<style fg="cyan"><b>></b></style> '))
|
|
430
472
|
|
|
@@ -437,7 +479,7 @@ class JsShell(ABC):
|
|
|
437
479
|
)
|
|
438
480
|
print(colorful_result, end="")
|
|
439
481
|
|
|
440
|
-
async def start(self, url: str = ""):
|
|
482
|
+
async def start(self, url: str = "") -> None:
|
|
441
483
|
if url:
|
|
442
484
|
await self.navigate(url)
|
|
443
485
|
while True:
|
|
@@ -456,45 +498,49 @@ class JsShell(ABC):
|
|
|
456
498
|
|
|
457
499
|
|
|
458
500
|
class AutomationJsShell(JsShell):
|
|
459
|
-
def __init__(self, driver: WebDriver):
|
|
501
|
+
def __init__(self, driver: WebDriver) -> None:
|
|
460
502
|
super().__init__()
|
|
461
|
-
self.driver = driver
|
|
503
|
+
self.driver: WebDriver = driver
|
|
462
504
|
|
|
463
505
|
@classmethod
|
|
464
506
|
@asynccontextmanager
|
|
465
|
-
async def create(
|
|
466
|
-
|
|
467
|
-
|
|
507
|
+
async def create(
|
|
508
|
+
cls, lockdown: LockdownServiceProvider, timeout: float, open_safari: bool
|
|
509
|
+
) -> "AsyncIterator[AutomationJsShell]":
|
|
510
|
+
inspector, application = await create_webinspector_and_launch_app(lockdown, timeout, SAFARI)
|
|
511
|
+
automation_session = await inspector.automation_session(application)
|
|
468
512
|
driver = WebDriver(automation_session)
|
|
469
513
|
await driver.start_session()
|
|
470
514
|
try:
|
|
471
515
|
yield cls(driver)
|
|
472
516
|
finally:
|
|
473
|
-
automation_session.stop_session()
|
|
474
|
-
inspector.close()
|
|
517
|
+
await automation_session.stop_session()
|
|
518
|
+
await inspector.close()
|
|
475
519
|
|
|
476
|
-
async def evaluate_expression(self, exp: str, return_by_value: bool = False):
|
|
520
|
+
async def evaluate_expression(self, exp: str, return_by_value: bool = False) -> Any:
|
|
477
521
|
return await self.driver.execute_script(f"return {exp}")
|
|
478
522
|
|
|
479
|
-
async def navigate(self, url: str):
|
|
523
|
+
async def navigate(self, url: str) -> None:
|
|
480
524
|
await self.driver.get(url)
|
|
481
525
|
|
|
482
526
|
|
|
483
527
|
class InspectorJsShell(JsShell):
|
|
484
|
-
def __init__(self, inspector_session: InspectorSession):
|
|
528
|
+
def __init__(self, inspector_session: InspectorSession) -> None:
|
|
485
529
|
super().__init__()
|
|
486
|
-
self.inspector_session = inspector_session
|
|
530
|
+
self.inspector_session: InspectorSession = inspector_session
|
|
487
531
|
|
|
488
532
|
@classmethod
|
|
489
533
|
@asynccontextmanager
|
|
490
|
-
async def create(
|
|
534
|
+
async def create(
|
|
535
|
+
cls, lockdown: LockdownServiceProvider, timeout: float, open_safari: bool
|
|
536
|
+
) -> "AsyncIterator[InspectorJsShell]":
|
|
491
537
|
inspector = WebinspectorService(lockdown=lockdown)
|
|
492
538
|
await inspector.connect(timeout)
|
|
493
539
|
if open_safari:
|
|
494
540
|
_ = await inspector.open_app(SAFARI)
|
|
495
541
|
application_page = await cls.query_page(inspector, bundle_identifier=SAFARI if open_safari else None)
|
|
496
542
|
if application_page is None:
|
|
497
|
-
raise
|
|
543
|
+
raise typer.Exit()
|
|
498
544
|
|
|
499
545
|
inspector_session = await inspector.inspector_session(application_page.application, application_page.page)
|
|
500
546
|
await inspector_session.console_enable()
|
|
@@ -505,7 +551,7 @@ class InspectorJsShell(JsShell):
|
|
|
505
551
|
finally:
|
|
506
552
|
await inspector.close()
|
|
507
553
|
|
|
508
|
-
async def evaluate_expression(self, exp: str, return_by_value: bool = False):
|
|
554
|
+
async def evaluate_expression(self, exp: str, return_by_value: bool = False) -> Any:
|
|
509
555
|
return await self.inspector_session.runtime_evaluate(exp, return_by_value=return_by_value)
|
|
510
556
|
|
|
511
557
|
async def navigate(self, url: str):
|
|
@@ -166,17 +166,18 @@ class RemoteServiceDiscoveryService(LockdownServiceProvider):
|
|
|
166
166
|
|
|
167
167
|
async def get_remoted_devices(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[RSDDevice]:
|
|
168
168
|
result = []
|
|
169
|
-
for
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
169
|
+
for instance in await browse_remoted(timeout):
|
|
170
|
+
for address in instance.addresses:
|
|
171
|
+
with RemoteServiceDiscoveryService((address.full_ip, RSD_PORT)) as rsd:
|
|
172
|
+
properties = rsd.peer_info["Properties"]
|
|
173
|
+
result.append(
|
|
174
|
+
RSDDevice(
|
|
175
|
+
hostname=address.full_ip,
|
|
176
|
+
udid=properties["UniqueDeviceID"],
|
|
177
|
+
product_type=properties["ProductType"],
|
|
178
|
+
os_version=properties["OSVersion"],
|
|
179
|
+
)
|
|
178
180
|
)
|
|
179
|
-
)
|
|
180
181
|
return result
|
|
181
182
|
|
|
182
183
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from contextlib import suppress
|
|
2
2
|
from functools import cached_property
|
|
3
|
-
from typing import Optional
|
|
3
|
+
from typing import Optional, overload
|
|
4
4
|
|
|
5
5
|
from pymobiledevice3.exceptions import MissingValueError
|
|
6
6
|
from pymobiledevice3.irecv import IRecv
|
|
@@ -8,9 +8,15 @@ from pymobiledevice3.lockdown import LockdownClient
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Device:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
@overload
|
|
12
|
+
def __init__(self, lockdown: LockdownClient, irecv: None = None) -> None: ...
|
|
13
|
+
|
|
14
|
+
@overload
|
|
15
|
+
def __init__(self, lockdown: None = None, *, irecv: IRecv) -> None: ...
|
|
16
|
+
|
|
17
|
+
def __init__(self, lockdown: Optional[LockdownClient] = None, irecv: Optional[IRecv] = None) -> None:
|
|
18
|
+
self._lockdown: Optional[LockdownClient] = lockdown
|
|
19
|
+
self._irecv: Optional[IRecv] = irecv
|
|
14
20
|
|
|
15
21
|
def __repr__(self) -> str:
|
|
16
22
|
return (
|
|
@@ -20,6 +26,24 @@ class Device:
|
|
|
20
26
|
f"image4-support: {self.is_image4_supported}>"
|
|
21
27
|
)
|
|
22
28
|
|
|
29
|
+
@cached_property
|
|
30
|
+
def is_lockdown(self) -> bool:
|
|
31
|
+
return self._lockdown is not None
|
|
32
|
+
|
|
33
|
+
@cached_property
|
|
34
|
+
def is_irecv(self) -> bool:
|
|
35
|
+
return self._irecv is not None
|
|
36
|
+
|
|
37
|
+
@cached_property
|
|
38
|
+
def lockdown(self) -> LockdownClient:
|
|
39
|
+
assert self._lockdown is not None
|
|
40
|
+
return self._lockdown
|
|
41
|
+
|
|
42
|
+
@cached_property
|
|
43
|
+
def irecv(self) -> IRecv:
|
|
44
|
+
assert self._irecv is not None
|
|
45
|
+
return self._irecv
|
|
46
|
+
|
|
23
47
|
@cached_property
|
|
24
48
|
def ecid(self):
|
|
25
49
|
if self.lockdown:
|
|
@@ -192,7 +192,7 @@ class ServiceConnection:
|
|
|
192
192
|
except ssl.SSLEOFError as e:
|
|
193
193
|
raise ConnectionTerminatedError from e
|
|
194
194
|
|
|
195
|
-
def send_recv_plist(self, data: dict, endianity: str = ">", fmt: Enum = plistlib.FMT_XML) -> Any:
|
|
195
|
+
def send_recv_plist(self, data: Union[dict, list], endianity: str = ">", fmt: Enum = plistlib.FMT_XML) -> Any:
|
|
196
196
|
"""
|
|
197
197
|
Send a plist to the socket and receive a plist response.
|
|
198
198
|
|
|
@@ -5,6 +5,7 @@ import uuid
|
|
|
5
5
|
from contextlib import contextmanager, suppress
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Union
|
|
8
9
|
|
|
9
10
|
from pymobiledevice3.exceptions import (
|
|
10
11
|
AfcException,
|
|
@@ -60,7 +61,9 @@ class Mobilebackup2Service(LockdownService):
|
|
|
60
61
|
except LockdownError:
|
|
61
62
|
return False
|
|
62
63
|
|
|
63
|
-
def backup(
|
|
64
|
+
def backup(
|
|
65
|
+
self, full: bool = True, backup_directory: Union[str, Path] = ".", progress_callback=lambda x: None
|
|
66
|
+
) -> None:
|
|
64
67
|
"""
|
|
65
68
|
Backup a device.
|
|
66
69
|
:param full: Whether to do a full backup. If full is True, any previous backup attempts will be discarded.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from pymobiledevice3.exceptions import PyMobileDevice3Exception
|
|
2
|
-
from pymobiledevice3.
|
|
2
|
+
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
3
3
|
from pymobiledevice3.services.lockdown_service import LockdownService
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class ScreenshotService(LockdownService):
|
|
7
7
|
SERVICE_NAME = "com.apple.mobile.screenshotr"
|
|
8
8
|
|
|
9
|
-
def __init__(self, lockdown:
|
|
9
|
+
def __init__(self, lockdown: LockdownServiceProvider) -> None:
|
|
10
10
|
super().__init__(lockdown, self.SERVICE_NAME, is_developer_service=True)
|
|
11
11
|
|
|
12
12
|
dl_message_version_exchange = self.service.recv_plist()
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import importlib.resources
|
|
1
2
|
import json
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from enum import Enum
|
|
4
|
-
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import pymobiledevice3.resources
|
|
7
|
+
|
|
8
|
+
RESOURCES = importlib.resources.files(pymobiledevice3.resources) / "webinspector"
|
|
7
9
|
FIND_NODES = (RESOURCES / "find_nodes.js").read_text()
|
|
8
10
|
|
|
9
11
|
|
|
@@ -3,6 +3,7 @@ import contextlib
|
|
|
3
3
|
from base64 import b64decode, b64encode
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from io import BytesIO
|
|
6
|
+
from typing import Optional
|
|
6
7
|
|
|
7
8
|
from PIL import Image
|
|
8
9
|
|
|
@@ -28,7 +29,7 @@ class ScreenCast:
|
|
|
28
29
|
self.device_height = 0
|
|
29
30
|
self.page_scale_factor = 0
|
|
30
31
|
self._run = True
|
|
31
|
-
self.recording_task
|
|
32
|
+
self.recording_task: Optional[asyncio.Task] = None
|
|
32
33
|
|
|
33
34
|
@property
|
|
34
35
|
def scale(self) -> float:
|
|
@@ -103,7 +103,7 @@ class WebElement(SeleniumApi):
|
|
|
103
103
|
@property
|
|
104
104
|
async def rect(self) -> Rect:
|
|
105
105
|
"""The size and location of the element."""
|
|
106
|
-
return await self._compute_layout(scroll_if_needed=False)[0]
|
|
106
|
+
return (await self._compute_layout(scroll_if_needed=False))[0]
|
|
107
107
|
|
|
108
108
|
@property
|
|
109
109
|
async def screenshot_as_base64(self) -> str:
|
|
@@ -144,7 +144,7 @@ class WebElement(SeleniumApi):
|
|
|
144
144
|
|
|
145
145
|
async def submit(self):
|
|
146
146
|
"""Submits a form."""
|
|
147
|
-
form = self.find_element(By.XPATH, "./ancestor-or-self::form")
|
|
147
|
+
form = await self.find_element(By.XPATH, "./ancestor-or-self::form")
|
|
148
148
|
submit_code = (
|
|
149
149
|
"var e = arguments[0].ownerDocument.createEvent('Event');"
|
|
150
150
|
"e.initEvent('submit', true, true);"
|
|
@@ -164,7 +164,7 @@ class WebElement(SeleniumApi):
|
|
|
164
164
|
'function(element) { return element.innerText.replace(/^[^\\S\\xa0]+|[^\\S\\xa0]+$/g, "") }'
|
|
165
165
|
)
|
|
166
166
|
|
|
167
|
-
async def touch(self):
|
|
167
|
+
async def touch(self) -> None:
|
|
168
168
|
"""Simulate touch interaction on the element."""
|
|
169
169
|
_rect, center, _is_obscured = await self._compute_layout(use_viewport=True)
|
|
170
170
|
await self.session.perform_interaction_sequence(
|