pymobiledevice3 5.0.4__py3-none-any.whl → 7.0.6__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.
- misc/understanding_idevice_protocol_layers.md +10 -5
- pymobiledevice3/__main__.py +171 -46
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +22 -21
- 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 +175 -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 +438 -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 +42 -0
- 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 +109 -100
- pymobiledevice3/cli/restore.py +134 -129
- pymobiledevice3/cli/springboard.py +50 -50
- pymobiledevice3/cli/syslog.py +145 -65
- pymobiledevice3/cli/usbmux.py +66 -27
- pymobiledevice3/cli/version.py +2 -5
- pymobiledevice3/cli/webinspector.py +232 -156
- pymobiledevice3/exceptions.py +6 -2
- pymobiledevice3/lockdown.py +5 -1
- pymobiledevice3/lockdown_service_provider.py +5 -0
- pymobiledevice3/remote/remote_service_discovery.py +18 -10
- pymobiledevice3/restore/device.py +28 -4
- pymobiledevice3/restore/restore.py +2 -2
- pymobiledevice3/service_connection.py +15 -12
- pymobiledevice3/services/afc.py +731 -220
- pymobiledevice3/services/device_link.py +45 -31
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/lockdown_service.py +12 -9
- pymobiledevice3/services/mobile_config.py +1 -0
- pymobiledevice3/services/mobilebackup2.py +6 -3
- pymobiledevice3/services/os_trace.py +97 -55
- pymobiledevice3/services/remote_fetch_symbols.py +13 -8
- pymobiledevice3/services/screenshot.py +2 -2
- pymobiledevice3/services/web_protocol/alert.py +8 -8
- pymobiledevice3/services/web_protocol/automation_session.py +87 -79
- pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
- pymobiledevice3/services/web_protocol/driver.py +71 -70
- pymobiledevice3/services/web_protocol/element.py +58 -56
- pymobiledevice3/services/web_protocol/selenium_api.py +47 -47
- pymobiledevice3/services/web_protocol/session_protocol.py +3 -2
- pymobiledevice3/services/web_protocol/switch_to.py +23 -19
- pymobiledevice3/services/webinspector.py +42 -67
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +5 -3
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/RECORD +76 -61
- pymobiledevice3/cli/completions.py +0 -50
- pymobiledevice3/cli/developer.py +0 -1539
- pymobiledevice3/cli/diagnostics.py +0 -110
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
|
@@ -1,28 +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
|
-
from
|
|
6
|
-
from
|
|
6
|
+
from asyncio import CancelledError
|
|
7
|
+
from collections.abc import AsyncIterator, Iterable
|
|
8
|
+
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
|
7
9
|
from functools import update_wrapper
|
|
8
|
-
from
|
|
10
|
+
from string import Template
|
|
11
|
+
from typing import Annotated, Any, Optional
|
|
9
12
|
|
|
10
|
-
import click
|
|
11
13
|
import inquirer3
|
|
12
14
|
import IPython
|
|
15
|
+
import nest_asyncio
|
|
16
|
+
import typer
|
|
13
17
|
import uvicorn
|
|
14
18
|
from inquirer3.themes import GreenPassion
|
|
15
19
|
from prompt_toolkit import HTML, PromptSession
|
|
16
20
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
17
|
-
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
|
|
18
23
|
from prompt_toolkit.history import FileHistory
|
|
19
24
|
from prompt_toolkit.lexers import PygmentsLexer
|
|
20
25
|
from prompt_toolkit.patch_stdout import patch_stdout
|
|
21
26
|
from prompt_toolkit.styles import style_from_pygments_cls
|
|
22
27
|
from pygments import formatters, highlight, lexers
|
|
23
28
|
from pygments.styles import get_style_by_name
|
|
29
|
+
from typer_injector import InjectingTyper
|
|
24
30
|
|
|
25
|
-
from pymobiledevice3.cli.cli_common import
|
|
31
|
+
from pymobiledevice3.cli.cli_common import ServiceProviderDep
|
|
26
32
|
from pymobiledevice3.common import get_home_folder
|
|
27
33
|
from pymobiledevice3.exceptions import (
|
|
28
34
|
InspectorEvaluateError,
|
|
@@ -31,35 +37,34 @@ from pymobiledevice3.exceptions import (
|
|
|
31
37
|
WebInspectorNotEnabledError,
|
|
32
38
|
WirError,
|
|
33
39
|
)
|
|
34
|
-
from pymobiledevice3.lockdown import
|
|
40
|
+
from pymobiledevice3.lockdown import create_using_usbmux
|
|
35
41
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
36
42
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
37
43
|
from pymobiledevice3.services.web_protocol.cdp_server import app
|
|
38
44
|
from pymobiledevice3.services.web_protocol.driver import By, Cookie, WebDriver
|
|
39
45
|
from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
|
|
40
|
-
from pymobiledevice3.services.webinspector import SAFARI, ApplicationPage, WebinspectorService
|
|
41
|
-
|
|
42
|
-
SCRIPT = """
|
|
43
|
-
function inspectedPage_evalResult_getCompletions(primitiveType) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}}
|
|
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
|
+
}
|
|
54
59
|
return resultSet;
|
|
55
|
-
}
|
|
60
|
+
}
|
|
56
61
|
|
|
57
|
-
try {
|
|
58
|
-
inspectedPage_evalResult_getCompletions({object})
|
|
59
|
-
}
|
|
60
|
-
"""
|
|
62
|
+
try {
|
|
63
|
+
inspectedPage_evalResult_getCompletions(${object})
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
""")
|
|
61
66
|
|
|
62
|
-
JS_RESERVED_WORDS =
|
|
67
|
+
JS_RESERVED_WORDS = frozenset({
|
|
63
68
|
"abstract",
|
|
64
69
|
"arguments",
|
|
65
70
|
"await",
|
|
@@ -124,54 +129,84 @@ JS_RESERVED_WORDS = [
|
|
|
124
129
|
"while",
|
|
125
130
|
"with",
|
|
126
131
|
"yield",
|
|
127
|
-
|
|
132
|
+
})
|
|
128
133
|
|
|
129
134
|
OSUTILS = get_os_utils()
|
|
130
135
|
logger = logging.getLogger(__name__)
|
|
131
136
|
|
|
132
137
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
+
)
|
|
136
146
|
|
|
137
147
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
}
|
|
142
154
|
|
|
155
|
+
def handle_error(e):
|
|
156
|
+
logger.error(next(msg for exc, msg in errors.items() if isinstance(e, exc)))
|
|
143
157
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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)
|
|
154
173
|
|
|
155
174
|
return update_wrapper(catch_function, func)
|
|
156
175
|
|
|
157
176
|
|
|
158
|
-
def reload_pages(inspector: WebinspectorService):
|
|
159
|
-
inspector.get_open_pages()
|
|
177
|
+
async def reload_pages(inspector: WebinspectorService) -> None:
|
|
178
|
+
await inspector.get_open_pages()
|
|
160
179
|
# Best effort.
|
|
161
|
-
inspector.flush_input(2)
|
|
180
|
+
await inspector.flush_input(2)
|
|
162
181
|
|
|
163
182
|
|
|
164
|
-
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]:
|
|
165
186
|
inspector = WebinspectorService(lockdown=lockdown)
|
|
166
|
-
inspector.connect(timeout)
|
|
167
|
-
application = inspector.open_app(app)
|
|
187
|
+
await inspector.connect(timeout)
|
|
188
|
+
application = await inspector.open_app(app)
|
|
168
189
|
return inspector, application
|
|
169
190
|
|
|
170
191
|
|
|
171
|
-
|
|
172
|
-
|
|
192
|
+
async def opened_tabs_task(service_provider: LockdownServiceProvider, timeout: float) -> None:
|
|
193
|
+
inspector = WebinspectorService(lockdown=service_provider)
|
|
194
|
+
await inspector.connect(timeout)
|
|
195
|
+
application_pages = await inspector.get_open_application_pages(timeout=timeout)
|
|
196
|
+
for application_page in application_pages:
|
|
197
|
+
print(application_page)
|
|
198
|
+
await inspector.close()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@cli.command()
|
|
173
202
|
@catch_errors
|
|
174
|
-
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:
|
|
175
210
|
"""
|
|
176
211
|
Show all currently opened tabs.
|
|
177
212
|
|
|
@@ -181,19 +216,33 @@ def opened_tabs(service_provider: LockdownClient, timeout):
|
|
|
181
216
|
|
|
182
217
|
iOS < 18: Settings -> Safari -> Advanced -> Web Inspector
|
|
183
218
|
"""
|
|
184
|
-
|
|
185
|
-
inspector.connect(timeout)
|
|
186
|
-
application_pages = inspector.get_open_application_pages(timeout=timeout)
|
|
187
|
-
for application_page in application_pages:
|
|
188
|
-
print(application_page)
|
|
189
|
-
inspector.close()
|
|
219
|
+
asyncio.run(opened_tabs_task(service_provider, timeout), debug=True)
|
|
190
220
|
|
|
191
221
|
|
|
192
|
-
@webinspector.command(cls=Command)
|
|
193
|
-
@click.argument("url")
|
|
194
|
-
@click.option("-t", "--timeout", default=3, show_default=True, type=float)
|
|
195
222
|
@catch_errors
|
|
196
|
-
def
|
|
223
|
+
async def launch_task(service_provider: LockdownServiceProvider, url, timeout) -> None:
|
|
224
|
+
inspector, safari = await create_webinspector_and_launch_app(service_provider, timeout, SAFARI)
|
|
225
|
+
session = await inspector.automation_session(safari)
|
|
226
|
+
driver = WebDriver(session)
|
|
227
|
+
print("Starting session")
|
|
228
|
+
await driver.start_session()
|
|
229
|
+
print("Getting URL")
|
|
230
|
+
await driver.get(url)
|
|
231
|
+
OSUTILS.wait_return()
|
|
232
|
+
await session.stop_session()
|
|
233
|
+
await inspector.close()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@cli.command()
|
|
237
|
+
@catch_errors
|
|
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:
|
|
197
246
|
"""
|
|
198
247
|
Launch a specific URL in Safari.
|
|
199
248
|
|
|
@@ -207,16 +256,7 @@ def launch(service_provider: LockdownClient, url, timeout):
|
|
|
207
256
|
Settings -> Safari -> Advanced -> Remote Automation
|
|
208
257
|
|
|
209
258
|
"""
|
|
210
|
-
|
|
211
|
-
session = inspector.automation_session(safari)
|
|
212
|
-
driver = WebDriver(session)
|
|
213
|
-
print("Starting session")
|
|
214
|
-
driver.start_session()
|
|
215
|
-
print("Getting URL")
|
|
216
|
-
driver.get(url)
|
|
217
|
-
OSUTILS.wait_return()
|
|
218
|
-
session.stop_session()
|
|
219
|
-
inspector.close()
|
|
259
|
+
asyncio.run(launch_task(service_provider, url, timeout), debug=True)
|
|
220
260
|
|
|
221
261
|
|
|
222
262
|
SHELL_USAGE = """
|
|
@@ -240,10 +280,35 @@ driver.add_cookie(
|
|
|
240
280
|
"""
|
|
241
281
|
|
|
242
282
|
|
|
243
|
-
@webinspector.command(cls=Command)
|
|
244
|
-
@click.option("-t", "--timeout", default=3, show_default=True, type=float)
|
|
245
283
|
@catch_errors
|
|
246
|
-
def
|
|
284
|
+
async def shell_task(service_provider: LockdownServiceProvider, timeout: float) -> None:
|
|
285
|
+
inspector, safari = await create_webinspector_and_launch_app(service_provider, timeout, SAFARI)
|
|
286
|
+
session = await inspector.automation_session(safari)
|
|
287
|
+
driver = WebDriver(session)
|
|
288
|
+
try:
|
|
289
|
+
nest_asyncio.apply()
|
|
290
|
+
IPython.embed(
|
|
291
|
+
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")),
|
|
292
|
+
user_ns={
|
|
293
|
+
"driver": driver,
|
|
294
|
+
"Cookie": Cookie,
|
|
295
|
+
"By": By,
|
|
296
|
+
},
|
|
297
|
+
)
|
|
298
|
+
finally:
|
|
299
|
+
await session.stop_session()
|
|
300
|
+
await inspector.close()
|
|
301
|
+
|
|
302
|
+
|
|
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:
|
|
247
312
|
"""
|
|
248
313
|
Create an IPython shell for interacting with a WebView.
|
|
249
314
|
|
|
@@ -256,31 +321,26 @@ def shell(service_provider: LockdownClient, timeout):
|
|
|
256
321
|
Settings -> Safari -> Advanced -> Web Inspector
|
|
257
322
|
Settings -> Safari -> Advanced -> Remote Automation
|
|
258
323
|
"""
|
|
259
|
-
|
|
260
|
-
session = inspector.automation_session(safari)
|
|
261
|
-
driver = WebDriver(session)
|
|
262
|
-
try:
|
|
263
|
-
IPython.embed(
|
|
264
|
-
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style="native")),
|
|
265
|
-
user_ns={
|
|
266
|
-
"driver": driver,
|
|
267
|
-
"Cookie": Cookie,
|
|
268
|
-
"By": By,
|
|
269
|
-
},
|
|
270
|
-
)
|
|
271
|
-
finally:
|
|
272
|
-
session.stop_session()
|
|
273
|
-
inspector.close()
|
|
324
|
+
asyncio.run(shell_task(service_provider, timeout), debug=True)
|
|
274
325
|
|
|
275
326
|
|
|
276
|
-
@
|
|
277
|
-
@click.option("-t", "--timeout", default=3, show_default=True, type=float)
|
|
278
|
-
@click.option("--automation", is_flag=True, help="Use remote automation")
|
|
279
|
-
@click.option("--no-open-safari", is_flag=True, help="Avoid opening the Safari app")
|
|
280
|
-
@click.argument("url", required=False, default="")
|
|
327
|
+
@cli.command()
|
|
281
328
|
@catch_errors
|
|
282
329
|
def js_shell(
|
|
283
|
-
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,
|
|
284
344
|
) -> None:
|
|
285
345
|
"""
|
|
286
346
|
Create a javascript shell. This interpreter runs on your local machine,
|
|
@@ -297,7 +357,7 @@ def js_shell(
|
|
|
297
357
|
"""
|
|
298
358
|
|
|
299
359
|
js_shell_class = AutomationJsShell if automation else InspectorJsShell
|
|
300
|
-
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))
|
|
301
361
|
|
|
302
362
|
|
|
303
363
|
udid = ""
|
|
@@ -309,10 +369,8 @@ def create_app():
|
|
|
309
369
|
return app
|
|
310
370
|
|
|
311
371
|
|
|
312
|
-
@
|
|
313
|
-
|
|
314
|
-
@click.option("--port", type=click.INT, default=9222)
|
|
315
|
-
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:
|
|
316
374
|
"""
|
|
317
375
|
Start a CDP server for debugging WebViews.
|
|
318
376
|
|
|
@@ -323,7 +381,7 @@ def cdp(service_provider: LockdownClient, host, port):
|
|
|
323
381
|
global udid
|
|
324
382
|
udid = service_provider.udid
|
|
325
383
|
uvicorn.run(
|
|
326
|
-
"
|
|
384
|
+
f"{__name__}:{create_app.__name__}",
|
|
327
385
|
host=host,
|
|
328
386
|
port=port,
|
|
329
387
|
factory=True,
|
|
@@ -333,47 +391,62 @@ def cdp(service_provider: LockdownClient, host, port):
|
|
|
333
391
|
)
|
|
334
392
|
|
|
335
393
|
|
|
336
|
-
def get_js_completions(jsshell: "JsShell", obj: str, prefix: str) ->
|
|
394
|
+
async def get_js_completions(jsshell: "JsShell", obj: str, prefix: str) -> AsyncIterator[Completion]:
|
|
337
395
|
if obj in JS_RESERVED_WORDS:
|
|
338
|
-
return
|
|
396
|
+
return
|
|
339
397
|
|
|
340
|
-
completions = []
|
|
341
398
|
try:
|
|
342
|
-
for key in
|
|
343
|
-
jsshell.evaluate_expression(SCRIPT.format(object=obj), return_by_value=True)
|
|
344
|
-
):
|
|
399
|
+
for key in await jsshell.evaluate_expression(SCRIPT.substitute(object=obj), return_by_value=True):
|
|
345
400
|
if not key.startswith(prefix):
|
|
346
401
|
continue
|
|
347
|
-
|
|
348
|
-
except Exception:
|
|
402
|
+
yield Completion(key.removeprefix(prefix), display=key)
|
|
403
|
+
except (Exception, CancelledError):
|
|
349
404
|
# ignore every possible exception
|
|
350
405
|
pass
|
|
351
|
-
return completions
|
|
352
406
|
|
|
353
407
|
|
|
354
408
|
class JsShellCompleter(Completer):
|
|
355
|
-
def __init__(self, jsshell: "JsShell"):
|
|
356
|
-
self.jsshell = jsshell
|
|
357
|
-
|
|
358
|
-
def
|
|
409
|
+
def __init__(self, jsshell: "JsShell") -> None:
|
|
410
|
+
self.jsshell: JsShell = jsshell
|
|
411
|
+
|
|
412
|
+
async def get_completions_async(
|
|
413
|
+
self,
|
|
414
|
+
document: Document,
|
|
415
|
+
complete_event: CompleteEvent,
|
|
416
|
+
) -> AsyncIterator[Completion]:
|
|
417
|
+
# Build the JS expression we want to inspect
|
|
359
418
|
text = f"globalThis.{document.text_before_cursor}"
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
419
|
+
|
|
420
|
+
# Extract identifiers / dotted paths
|
|
421
|
+
matches = re.findall(r"[a-zA-Z_][a-zA-Z_0-9.]+", text)
|
|
422
|
+
if not matches:
|
|
423
|
+
# async *generator*: just end, don't return a list
|
|
424
|
+
return
|
|
425
|
+
|
|
426
|
+
text = matches[-1]
|
|
364
427
|
if "." in text:
|
|
365
428
|
js_obj, prefix = text.rsplit(".", 1)
|
|
366
429
|
else:
|
|
367
430
|
js_obj = text
|
|
368
431
|
prefix = ""
|
|
369
432
|
|
|
370
|
-
return
|
|
433
|
+
# This should return an iterable of Completion (or something we can wrap)
|
|
434
|
+
async for completion in get_js_completions(self.jsshell, js_obj, prefix):
|
|
435
|
+
yield completion
|
|
436
|
+
|
|
437
|
+
# Optional: keep sync completions empty so PTK knows we prefer async
|
|
438
|
+
def get_completions(
|
|
439
|
+
self,
|
|
440
|
+
document: Document,
|
|
441
|
+
complete_event: CompleteEvent,
|
|
442
|
+
) -> Iterable[Completion]:
|
|
443
|
+
return []
|
|
371
444
|
|
|
372
445
|
|
|
373
446
|
class JsShell(ABC):
|
|
374
447
|
def __init__(self) -> None:
|
|
375
448
|
super().__init__()
|
|
376
|
-
self.prompt_session = PromptSession(
|
|
449
|
+
self.prompt_session: PromptSession = PromptSession(
|
|
377
450
|
lexer=PygmentsLexer(lexers.JavascriptLexer),
|
|
378
451
|
auto_suggest=AutoSuggestFromHistory(),
|
|
379
452
|
style=style_from_pygments_cls(get_style_by_name("stata-dark")),
|
|
@@ -383,18 +456,17 @@ class JsShell(ABC):
|
|
|
383
456
|
|
|
384
457
|
@classmethod
|
|
385
458
|
@abstractmethod
|
|
386
|
-
def create(
|
|
387
|
-
|
|
459
|
+
def create(
|
|
460
|
+
cls, lockdown: LockdownServiceProvider, timeout: float, open_safari: bool
|
|
461
|
+
) -> "AbstractAsyncContextManager[JsShell]": ...
|
|
388
462
|
|
|
389
463
|
@abstractmethod
|
|
390
|
-
async def evaluate_expression(self, exp, return_by_value: bool = False):
|
|
391
|
-
pass
|
|
464
|
+
async def evaluate_expression(self, exp, return_by_value: bool = False) -> Any: ...
|
|
392
465
|
|
|
393
466
|
@abstractmethod
|
|
394
|
-
async def navigate(self, url: str):
|
|
395
|
-
pass
|
|
467
|
+
async def navigate(self, url: str) -> None: ...
|
|
396
468
|
|
|
397
|
-
async def js_iter(self):
|
|
469
|
+
async def js_iter(self) -> None:
|
|
398
470
|
with patch_stdout(True):
|
|
399
471
|
exp = await self.prompt_session.prompt_async(HTML('<style fg="cyan"><b>></b></style> '))
|
|
400
472
|
|
|
@@ -407,14 +479,14 @@ class JsShell(ABC):
|
|
|
407
479
|
)
|
|
408
480
|
print(colorful_result, end="")
|
|
409
481
|
|
|
410
|
-
async def start(self, url: str = ""):
|
|
482
|
+
async def start(self, url: str = "") -> None:
|
|
411
483
|
if url:
|
|
412
484
|
await self.navigate(url)
|
|
413
485
|
while True:
|
|
414
486
|
try:
|
|
415
487
|
await self.js_iter()
|
|
416
|
-
except (WirError, InspectorEvaluateError):
|
|
417
|
-
logger.error(
|
|
488
|
+
except (WirError, InspectorEvaluateError) as e:
|
|
489
|
+
logger.error(e)
|
|
418
490
|
except KeyboardInterrupt: # KeyboardInterrupt Control-C
|
|
419
491
|
pass
|
|
420
492
|
except EOFError: # Control-D
|
|
@@ -426,45 +498,49 @@ class JsShell(ABC):
|
|
|
426
498
|
|
|
427
499
|
|
|
428
500
|
class AutomationJsShell(JsShell):
|
|
429
|
-
def __init__(self, driver: WebDriver):
|
|
501
|
+
def __init__(self, driver: WebDriver) -> None:
|
|
430
502
|
super().__init__()
|
|
431
|
-
self.driver = driver
|
|
503
|
+
self.driver: WebDriver = driver
|
|
432
504
|
|
|
433
505
|
@classmethod
|
|
434
506
|
@asynccontextmanager
|
|
435
|
-
async def create(
|
|
436
|
-
|
|
437
|
-
|
|
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)
|
|
438
512
|
driver = WebDriver(automation_session)
|
|
439
|
-
driver.start_session()
|
|
513
|
+
await driver.start_session()
|
|
440
514
|
try:
|
|
441
515
|
yield cls(driver)
|
|
442
516
|
finally:
|
|
443
|
-
automation_session.stop_session()
|
|
444
|
-
inspector.close()
|
|
517
|
+
await automation_session.stop_session()
|
|
518
|
+
await inspector.close()
|
|
445
519
|
|
|
446
|
-
async def evaluate_expression(self, exp: str, return_by_value: bool = False):
|
|
447
|
-
return self.driver.execute_script(f"return {exp}")
|
|
520
|
+
async def evaluate_expression(self, exp: str, return_by_value: bool = False) -> Any:
|
|
521
|
+
return await self.driver.execute_script(f"return {exp}")
|
|
448
522
|
|
|
449
|
-
async def navigate(self, url: str):
|
|
450
|
-
self.driver.get(url)
|
|
523
|
+
async def navigate(self, url: str) -> None:
|
|
524
|
+
await self.driver.get(url)
|
|
451
525
|
|
|
452
526
|
|
|
453
527
|
class InspectorJsShell(JsShell):
|
|
454
|
-
def __init__(self, inspector_session: InspectorSession):
|
|
528
|
+
def __init__(self, inspector_session: InspectorSession) -> None:
|
|
455
529
|
super().__init__()
|
|
456
|
-
self.inspector_session = inspector_session
|
|
530
|
+
self.inspector_session: InspectorSession = inspector_session
|
|
457
531
|
|
|
458
532
|
@classmethod
|
|
459
533
|
@asynccontextmanager
|
|
460
|
-
async def create(
|
|
534
|
+
async def create(
|
|
535
|
+
cls, lockdown: LockdownServiceProvider, timeout: float, open_safari: bool
|
|
536
|
+
) -> "AsyncIterator[InspectorJsShell]":
|
|
461
537
|
inspector = WebinspectorService(lockdown=lockdown)
|
|
462
|
-
inspector.connect(timeout)
|
|
538
|
+
await inspector.connect(timeout)
|
|
463
539
|
if open_safari:
|
|
464
|
-
_ = inspector.open_app(SAFARI)
|
|
465
|
-
application_page = cls.query_page(inspector, bundle_identifier=SAFARI if open_safari else None)
|
|
540
|
+
_ = await inspector.open_app(SAFARI)
|
|
541
|
+
application_page = await cls.query_page(inspector, bundle_identifier=SAFARI if open_safari else None)
|
|
466
542
|
if application_page is None:
|
|
467
|
-
raise
|
|
543
|
+
raise typer.Exit()
|
|
468
544
|
|
|
469
545
|
inspector_session = await inspector.inspector_session(application_page.application, application_page.page)
|
|
470
546
|
await inspector_session.console_enable()
|
|
@@ -473,19 +549,19 @@ class InspectorJsShell(JsShell):
|
|
|
473
549
|
try:
|
|
474
550
|
yield cls(inspector_session)
|
|
475
551
|
finally:
|
|
476
|
-
inspector.close()
|
|
552
|
+
await inspector.close()
|
|
477
553
|
|
|
478
|
-
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:
|
|
479
555
|
return await self.inspector_session.runtime_evaluate(exp, return_by_value=return_by_value)
|
|
480
556
|
|
|
481
557
|
async def navigate(self, url: str):
|
|
482
558
|
await self.inspector_session.navigate_to_url(url)
|
|
483
559
|
|
|
484
560
|
@staticmethod
|
|
485
|
-
def query_page(
|
|
561
|
+
async def query_page(
|
|
486
562
|
inspector: WebinspectorService, bundle_identifier: Optional[str] = None
|
|
487
563
|
) -> Optional[ApplicationPage]:
|
|
488
|
-
available_pages = inspector.get_open_application_pages(timeout=1)
|
|
564
|
+
available_pages = await inspector.get_open_application_pages(timeout=1)
|
|
489
565
|
if bundle_identifier is not None:
|
|
490
566
|
available_pages = [
|
|
491
567
|
application_page
|
pymobiledevice3/exceptions.py
CHANGED
|
@@ -107,7 +107,10 @@ class PasswordRequiredError(PairingError):
|
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
class StartServiceError(PyMobileDevice3Exception):
|
|
110
|
-
|
|
110
|
+
def __init__(self, service_name: str, message: str) -> None:
|
|
111
|
+
super().__init__()
|
|
112
|
+
self.service_name = service_name
|
|
113
|
+
self.message = message
|
|
111
114
|
|
|
112
115
|
|
|
113
116
|
class FatalPairingError(PyMobileDevice3Exception):
|
|
@@ -163,9 +166,10 @@ class ArgumentError(PyMobileDevice3Exception):
|
|
|
163
166
|
|
|
164
167
|
|
|
165
168
|
class AfcException(PyMobileDevice3Exception, OSError):
|
|
166
|
-
def __init__(self, message, status):
|
|
169
|
+
def __init__(self, message: str, status: str, filename: Optional[str] = None) -> None:
|
|
167
170
|
OSError.__init__(self, status, message)
|
|
168
171
|
self.status = status
|
|
172
|
+
self.filename = filename
|
|
169
173
|
|
|
170
174
|
|
|
171
175
|
class AfcFileNotFoundError(AfcException):
|
pymobiledevice3/lockdown.py
CHANGED
|
@@ -221,6 +221,10 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
221
221
|
def product_version(self) -> str:
|
|
222
222
|
return self.all_values.get("ProductVersion") or "1.0"
|
|
223
223
|
|
|
224
|
+
@property
|
|
225
|
+
def product_build_version(self) -> str:
|
|
226
|
+
return self.all_values.get("BuildVersion")
|
|
227
|
+
|
|
224
228
|
@property
|
|
225
229
|
def device_class(self) -> DeviceClass:
|
|
226
230
|
try:
|
|
@@ -576,7 +580,7 @@ class LockdownClient(ABC, LockdownServiceProvider):
|
|
|
576
580
|
raise PasswordRequiredError(
|
|
577
581
|
"your device is protected with password, please enter password in device and try again"
|
|
578
582
|
)
|
|
579
|
-
raise StartServiceError(response.get("Error"))
|
|
583
|
+
raise StartServiceError(name, response.get("Error"))
|
|
580
584
|
return response
|
|
581
585
|
|
|
582
586
|
@_reconnect_on_remote_close
|