pymobiledevice3 4.14.6__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/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- misc/understanding_idevice_protocol_layers.md +15 -10
- pymobiledevice3/__main__.py +317 -127
- pymobiledevice3/_version.py +22 -4
- pymobiledevice3/bonjour.py +358 -113
- pymobiledevice3/ca.py +253 -16
- pymobiledevice3/cli/activation.py +31 -23
- pymobiledevice3/cli/afc.py +49 -40
- pymobiledevice3/cli/amfi.py +16 -21
- pymobiledevice3/cli/apps.py +87 -42
- pymobiledevice3/cli/backup.py +160 -90
- pymobiledevice3/cli/bonjour.py +44 -40
- pymobiledevice3/cli/cli_common.py +204 -198
- pymobiledevice3/cli/companion_proxy.py +14 -14
- pymobiledevice3/cli/crash.py +105 -56
- 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 +108 -103
- pymobiledevice3/cli/mounter.py +158 -99
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +45 -24
- pymobiledevice3/cli/power_assertion.py +18 -17
- pymobiledevice3/cli/processes.py +17 -23
- pymobiledevice3/cli/profile.py +165 -109
- pymobiledevice3/cli/provision.py +35 -34
- pymobiledevice3/cli/remote.py +217 -129
- pymobiledevice3/cli/restore.py +159 -143
- pymobiledevice3/cli/springboard.py +63 -53
- pymobiledevice3/cli/syslog.py +193 -86
- pymobiledevice3/cli/usbmux.py +73 -33
- pymobiledevice3/cli/version.py +5 -7
- pymobiledevice3/cli/webinspector.py +376 -214
- pymobiledevice3/common.py +3 -1
- pymobiledevice3/exceptions.py +182 -58
- pymobiledevice3/irecv.py +52 -53
- pymobiledevice3/irecv_devices.py +1489 -464
- pymobiledevice3/lockdown.py +473 -275
- pymobiledevice3/lockdown_service_provider.py +15 -8
- pymobiledevice3/osu/os_utils.py +27 -9
- pymobiledevice3/osu/posix_util.py +34 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +102 -21
- pymobiledevice3/remote/common.py +8 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
- pymobiledevice3/remote/core_device/file_service.py +53 -23
- pymobiledevice3/remote/remote_service_discovery.py +79 -45
- pymobiledevice3/remote/remotexpc.py +73 -44
- pymobiledevice3/remote/tunnel_service.py +442 -317
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +20 -16
- pymobiledevice3/resources/notifications.txt +144 -0
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +110 -21
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +59 -12
- pymobiledevice3/restore/fdr.py +46 -48
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +163 -0
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +151 -151
- pymobiledevice3/restore/restore.py +562 -544
- pymobiledevice3/restore/restore_options.py +131 -110
- pymobiledevice3/restore/restored_client.py +51 -31
- pymobiledevice3/restore/tss.py +385 -267
- pymobiledevice3/service_connection.py +252 -59
- pymobiledevice3/services/accessibilityaudit.py +202 -120
- pymobiledevice3/services/afc.py +962 -365
- pymobiledevice3/services/amfi.py +24 -30
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +71 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +101 -79
- pymobiledevice3/services/diagnostics.py +973 -967
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +9 -8
- pymobiledevice3/services/house_arrest.py +16 -15
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/installation_proxy.py +173 -81
- pymobiledevice3/services/lockdown_service.py +20 -10
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +147 -64
- pymobiledevice3/services/mobile_config.py +331 -294
- pymobiledevice3/services/mobile_image_mounter.py +141 -113
- pymobiledevice3/services/mobilebackup2.py +203 -145
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +134 -74
- pymobiledevice3/services/pcapd.py +314 -302
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +21 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +15 -12
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +11 -11
- pymobiledevice3/services/web_protocol/automation_session.py +251 -239
- pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +114 -111
- pymobiledevice3/services/web_protocol/element.py +124 -111
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
- pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
- pymobiledevice3/services/web_protocol/switch_to.py +30 -27
- pymobiledevice3/services/webinspector.py +189 -155
- pymobiledevice3/tcp_forwarder.py +87 -69
- pymobiledevice3/tunneld/__init__.py +0 -0
- pymobiledevice3/tunneld/api.py +63 -0
- pymobiledevice3/tunneld/server.py +603 -0
- pymobiledevice3/usbmux.py +198 -147
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
- pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
- pymobiledevice3/cli/developer.py +0 -1215
- pymobiledevice3/cli/diagnostics.py +0 -99
- pymobiledevice3/tunneld.py +0 -524
- pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
|
@@ -1,166 +1,265 @@
|
|
|
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
|
-
from pymobiledevice3.exceptions import
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
from pymobiledevice3.exceptions import (
|
|
34
|
+
InspectorEvaluateError,
|
|
35
|
+
LaunchingApplicationError,
|
|
36
|
+
RemoteAutomationNotEnabledError,
|
|
37
|
+
WebInspectorNotEnabledError,
|
|
38
|
+
WirError,
|
|
39
|
+
)
|
|
40
|
+
from pymobiledevice3.lockdown import create_using_usbmux
|
|
41
|
+
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
30
42
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
31
43
|
from pymobiledevice3.services.web_protocol.cdp_server import app
|
|
32
44
|
from pymobiledevice3.services.web_protocol.driver import By, Cookie, WebDriver
|
|
33
45
|
from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
|
|
34
|
-
from pymobiledevice3.services.webinspector import SAFARI,
|
|
35
|
-
|
|
36
|
-
SCRIPT =
|
|
37
|
-
function inspectedPage_evalResult_getCompletions(primitiveType) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
for(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}}
|
|
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
|
+
}
|
|
48
59
|
return resultSet;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
inspectedPage_evalResult_getCompletions({object})
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
JS_RESERVED_WORDS =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
inspectedPage_evalResult_getCompletions(${object})
|
|
64
|
+
} catch (e) {}
|
|
65
|
+
""")
|
|
66
|
+
|
|
67
|
+
JS_RESERVED_WORDS = frozenset({
|
|
68
|
+
"abstract",
|
|
69
|
+
"arguments",
|
|
70
|
+
"await",
|
|
71
|
+
"boolean",
|
|
72
|
+
"break",
|
|
73
|
+
"byte",
|
|
74
|
+
"case",
|
|
75
|
+
"catch",
|
|
76
|
+
"char",
|
|
77
|
+
"class",
|
|
78
|
+
"const",
|
|
79
|
+
"continue",
|
|
80
|
+
"debugger",
|
|
81
|
+
"default",
|
|
82
|
+
"delete",
|
|
83
|
+
"do",
|
|
84
|
+
"double",
|
|
85
|
+
"else",
|
|
86
|
+
"enum",
|
|
87
|
+
"eval",
|
|
88
|
+
"export",
|
|
89
|
+
"extends",
|
|
90
|
+
"false",
|
|
91
|
+
"final",
|
|
92
|
+
"finally",
|
|
93
|
+
"float",
|
|
94
|
+
"for",
|
|
95
|
+
"function",
|
|
96
|
+
"goto",
|
|
97
|
+
"if",
|
|
98
|
+
"implements",
|
|
99
|
+
"import",
|
|
100
|
+
"in",
|
|
101
|
+
"instanceof",
|
|
102
|
+
"int",
|
|
103
|
+
"interface",
|
|
104
|
+
"let",
|
|
105
|
+
"long",
|
|
106
|
+
"native",
|
|
107
|
+
"new",
|
|
108
|
+
"null",
|
|
109
|
+
"package",
|
|
110
|
+
"private",
|
|
111
|
+
"protected",
|
|
112
|
+
"public",
|
|
113
|
+
"return",
|
|
114
|
+
"short",
|
|
115
|
+
"static",
|
|
116
|
+
"super",
|
|
117
|
+
"switch",
|
|
118
|
+
"synchronized",
|
|
119
|
+
"this",
|
|
120
|
+
"throw",
|
|
121
|
+
"throws",
|
|
122
|
+
"transient",
|
|
123
|
+
"true",
|
|
124
|
+
"try",
|
|
125
|
+
"typeof",
|
|
126
|
+
"var",
|
|
127
|
+
"void",
|
|
128
|
+
"volatile",
|
|
129
|
+
"while",
|
|
130
|
+
"with",
|
|
131
|
+
"yield",
|
|
132
|
+
})
|
|
63
133
|
|
|
64
134
|
OSUTILS = get_os_utils()
|
|
65
135
|
logger = logging.getLogger(__name__)
|
|
66
136
|
|
|
67
137
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
+
)
|
|
71
146
|
|
|
72
147
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
}
|
|
77
154
|
|
|
155
|
+
def handle_error(e):
|
|
156
|
+
logger.error(next(msg for exc, msg in errors.items() if isinstance(e, exc)))
|
|
78
157
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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)
|
|
89
173
|
|
|
90
174
|
return update_wrapper(catch_function, func)
|
|
91
175
|
|
|
92
176
|
|
|
93
|
-
def reload_pages(inspector: WebinspectorService):
|
|
94
|
-
inspector.get_open_pages()
|
|
177
|
+
async def reload_pages(inspector: WebinspectorService) -> None:
|
|
178
|
+
await inspector.get_open_pages()
|
|
95
179
|
# Best effort.
|
|
96
|
-
inspector.flush_input(2)
|
|
180
|
+
await inspector.flush_input(2)
|
|
97
181
|
|
|
98
182
|
|
|
99
|
-
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]:
|
|
100
186
|
inspector = WebinspectorService(lockdown=lockdown)
|
|
101
|
-
inspector.connect(timeout)
|
|
102
|
-
application = inspector.open_app(app)
|
|
187
|
+
await inspector.connect(timeout)
|
|
188
|
+
application = await inspector.open_app(app)
|
|
103
189
|
return inspector, application
|
|
104
190
|
|
|
105
191
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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()
|
|
109
202
|
@catch_errors
|
|
110
|
-
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:
|
|
111
210
|
"""
|
|
112
211
|
Show all currently opened tabs.
|
|
113
212
|
|
|
114
213
|
\b
|
|
115
214
|
Opt-in:
|
|
116
|
-
|
|
215
|
+
iOS >= 18: Settings -> Apps -> Safari -> Advanced -> Web Inspector
|
|
216
|
+
|
|
217
|
+
iOS < 18: Settings -> Safari -> Advanced -> Web Inspector
|
|
117
218
|
"""
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
@webinspector.command(cls=Command)
|
|
139
|
-
@click.argument('url')
|
|
140
|
-
@click.option('-t', '--timeout', default=3, show_default=True, type=float)
|
|
219
|
+
asyncio.run(opened_tabs_task(service_provider, timeout), debug=True)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@catch_errors
|
|
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()
|
|
141
237
|
@catch_errors
|
|
142
|
-
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:
|
|
143
246
|
"""
|
|
144
247
|
Launch a specific URL in Safari.
|
|
145
248
|
|
|
146
249
|
\b
|
|
147
|
-
Opt-in:
|
|
250
|
+
Opt-in (iOS >= 18):
|
|
251
|
+
Settings -> Apps -> Safari -> Advanced -> Web Inspector
|
|
252
|
+
Settings -> Apps -> Safari -> Advanced -> Remote Automation
|
|
253
|
+
|
|
254
|
+
Opt-in (iOS < 18):
|
|
148
255
|
Settings -> Safari -> Advanced -> Web Inspector
|
|
149
256
|
Settings -> Safari -> Advanced -> Remote Automation
|
|
257
|
+
|
|
150
258
|
"""
|
|
151
|
-
|
|
152
|
-
session = inspector.automation_session(safari)
|
|
153
|
-
driver = WebDriver(session)
|
|
154
|
-
print('Starting session')
|
|
155
|
-
driver.start_session()
|
|
156
|
-
print('Getting URL')
|
|
157
|
-
driver.get(url)
|
|
158
|
-
OSUTILS.wait_return()
|
|
159
|
-
session.stop_session()
|
|
160
|
-
inspector.close()
|
|
259
|
+
asyncio.run(launch_task(service_provider, url, timeout), debug=True)
|
|
161
260
|
|
|
162
261
|
|
|
163
|
-
SHELL_USAGE =
|
|
262
|
+
SHELL_USAGE = """
|
|
164
263
|
# This shell allows you to control the web with selenium like API.
|
|
165
264
|
# The first thing you should do is creating a session:
|
|
166
265
|
driver.start_session()
|
|
@@ -178,61 +277,90 @@ driver.add_cookie(
|
|
|
178
277
|
)
|
|
179
278
|
|
|
180
279
|
# See selenium api for more features.
|
|
181
|
-
|
|
280
|
+
"""
|
|
182
281
|
|
|
183
282
|
|
|
184
|
-
@webinspector.command(cls=Command)
|
|
185
|
-
@click.option('-t', '--timeout', default=3, show_default=True, type=float)
|
|
186
283
|
@catch_errors
|
|
187
|
-
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:
|
|
188
312
|
"""
|
|
189
313
|
Create an IPython shell for interacting with a WebView.
|
|
190
314
|
|
|
191
315
|
\b
|
|
192
|
-
Opt-in:
|
|
316
|
+
Opt-in (iOS >= 18):
|
|
317
|
+
Settings -> Apps -> Safari -> Advanced -> Web Inspector
|
|
318
|
+
Settings -> Apps -> Safari -> Advanced -> Remote Automation
|
|
319
|
+
|
|
320
|
+
Opt-in (iOS < 18):
|
|
193
321
|
Settings -> Safari -> Advanced -> Web Inspector
|
|
194
322
|
Settings -> Safari -> Advanced -> Remote Automation
|
|
195
323
|
"""
|
|
196
|
-
|
|
197
|
-
session = inspector.automation_session(safari)
|
|
198
|
-
driver = WebDriver(session)
|
|
199
|
-
try:
|
|
200
|
-
IPython.embed(
|
|
201
|
-
header=highlight(SHELL_USAGE, lexers.PythonLexer(), formatters.Terminal256Formatter(style='native')),
|
|
202
|
-
user_ns={
|
|
203
|
-
'driver': driver,
|
|
204
|
-
'Cookie': Cookie,
|
|
205
|
-
'By': By,
|
|
206
|
-
})
|
|
207
|
-
finally:
|
|
208
|
-
session.stop_session()
|
|
209
|
-
inspector.close()
|
|
324
|
+
asyncio.run(shell_task(service_provider, timeout), debug=True)
|
|
210
325
|
|
|
211
326
|
|
|
212
|
-
@
|
|
213
|
-
@click.option('-t', '--timeout', default=3, show_default=True, type=float)
|
|
214
|
-
@click.option('--automation', is_flag=True, help='Use remote automation')
|
|
215
|
-
@click.argument('url', required=False, default='')
|
|
327
|
+
@cli.command()
|
|
216
328
|
@catch_errors
|
|
217
|
-
def js_shell(
|
|
329
|
+
def js_shell(
|
|
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,
|
|
344
|
+
) -> None:
|
|
218
345
|
"""
|
|
219
346
|
Create a javascript shell. This interpreter runs on your local machine,
|
|
220
347
|
but evaluates each expression on the remote
|
|
221
348
|
|
|
222
349
|
\b
|
|
223
350
|
Opt-in:
|
|
224
|
-
Settings -> Safari -> Advanced -> Web Inspector
|
|
225
|
-
|
|
351
|
+
iOS >= 18: Settings -> Apps -> Safari -> Advanced -> Web Inspector
|
|
352
|
+
iOS < 18: Settings -> Safari -> Advanced -> Web Inspector
|
|
226
353
|
\b
|
|
227
354
|
for automation also enable:
|
|
228
|
-
Settings -> Safari -> Advanced -> Remote Automation
|
|
355
|
+
iOS >= 18: Settings -> Apps -> Safari -> Advanced -> Remote Automation
|
|
356
|
+
iOS < 18: Settings -> Safari -> Advanced -> Remote Automation
|
|
229
357
|
"""
|
|
230
358
|
|
|
231
359
|
js_shell_class = AutomationJsShell if automation else InspectorJsShell
|
|
232
|
-
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))
|
|
233
361
|
|
|
234
362
|
|
|
235
|
-
udid =
|
|
363
|
+
udid = ""
|
|
236
364
|
|
|
237
365
|
|
|
238
366
|
def create_app():
|
|
@@ -241,10 +369,8 @@ def create_app():
|
|
|
241
369
|
return app
|
|
242
370
|
|
|
243
371
|
|
|
244
|
-
@
|
|
245
|
-
|
|
246
|
-
@click.option('--port', type=click.INT, default=9222)
|
|
247
|
-
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:
|
|
248
374
|
"""
|
|
249
375
|
Start a CDP server for debugging WebViews.
|
|
250
376
|
|
|
@@ -254,71 +380,93 @@ def cdp(service_provider: LockdownClient, host, port):
|
|
|
254
380
|
"""
|
|
255
381
|
global udid
|
|
256
382
|
udid = service_provider.udid
|
|
257
|
-
uvicorn.run(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
383
|
+
uvicorn.run(
|
|
384
|
+
f"{__name__}:{create_app.__name__}",
|
|
385
|
+
host=host,
|
|
386
|
+
port=port,
|
|
387
|
+
factory=True,
|
|
388
|
+
ws_ping_timeout=None,
|
|
389
|
+
ws="wsproto",
|
|
390
|
+
loop="asyncio",
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
async def get_js_completions(jsshell: "JsShell", obj: str, prefix: str) -> AsyncIterator[Completion]:
|
|
262
395
|
if obj in JS_RESERVED_WORDS:
|
|
263
|
-
return
|
|
396
|
+
return
|
|
264
397
|
|
|
265
|
-
completions = []
|
|
266
398
|
try:
|
|
267
|
-
for key in
|
|
268
|
-
jsshell.evaluate_expression(SCRIPT.format(object=obj), return_by_value=True)):
|
|
399
|
+
for key in await jsshell.evaluate_expression(SCRIPT.substitute(object=obj), return_by_value=True):
|
|
269
400
|
if not key.startswith(prefix):
|
|
270
401
|
continue
|
|
271
|
-
|
|
272
|
-
except Exception:
|
|
402
|
+
yield Completion(key.removeprefix(prefix), display=key)
|
|
403
|
+
except (Exception, CancelledError):
|
|
273
404
|
# ignore every possible exception
|
|
274
405
|
pass
|
|
275
|
-
return completions
|
|
276
406
|
|
|
277
407
|
|
|
278
408
|
class JsShellCompleter(Completer):
|
|
279
|
-
def __init__(self, jsshell:
|
|
280
|
-
self.jsshell = jsshell
|
|
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
|
|
418
|
+
text = f"globalThis.{document.text_before_cursor}"
|
|
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
|
|
281
425
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
text = f'globalThis.{document.text_before_cursor}'
|
|
286
|
-
text = re.findall('[a-zA-Z_][a-zA-Z_0-9.]+', text)
|
|
287
|
-
if len(text) == 0:
|
|
288
|
-
return []
|
|
289
|
-
text = text[-1]
|
|
290
|
-
if '.' in text:
|
|
291
|
-
js_obj, prefix = text.rsplit('.', 1)
|
|
426
|
+
text = matches[-1]
|
|
427
|
+
if "." in text:
|
|
428
|
+
js_obj, prefix = text.rsplit(".", 1)
|
|
292
429
|
else:
|
|
293
430
|
js_obj = text
|
|
294
|
-
prefix =
|
|
431
|
+
prefix = ""
|
|
295
432
|
|
|
296
|
-
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 []
|
|
297
444
|
|
|
298
445
|
|
|
299
446
|
class JsShell(ABC):
|
|
300
|
-
def __init__(self):
|
|
447
|
+
def __init__(self) -> None:
|
|
301
448
|
super().__init__()
|
|
302
|
-
self.prompt_session = PromptSession(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
449
|
+
self.prompt_session: PromptSession = PromptSession(
|
|
450
|
+
lexer=PygmentsLexer(lexers.JavascriptLexer),
|
|
451
|
+
auto_suggest=AutoSuggestFromHistory(),
|
|
452
|
+
style=style_from_pygments_cls(get_style_by_name("stata-dark")),
|
|
453
|
+
history=FileHistory(self.webinspector_history_path()),
|
|
454
|
+
completer=JsShellCompleter(self),
|
|
455
|
+
)
|
|
307
456
|
|
|
308
457
|
@classmethod
|
|
309
458
|
@abstractmethod
|
|
310
|
-
def create(
|
|
311
|
-
|
|
459
|
+
def create(
|
|
460
|
+
cls, lockdown: LockdownServiceProvider, timeout: float, open_safari: bool
|
|
461
|
+
) -> "AbstractAsyncContextManager[JsShell]": ...
|
|
312
462
|
|
|
313
463
|
@abstractmethod
|
|
314
|
-
async def evaluate_expression(self, exp, return_by_value: bool = False):
|
|
315
|
-
pass
|
|
464
|
+
async def evaluate_expression(self, exp, return_by_value: bool = False) -> Any: ...
|
|
316
465
|
|
|
317
466
|
@abstractmethod
|
|
318
|
-
async def navigate(self, url: str):
|
|
319
|
-
pass
|
|
467
|
+
async def navigate(self, url: str) -> None: ...
|
|
320
468
|
|
|
321
|
-
async def js_iter(self):
|
|
469
|
+
async def js_iter(self) -> None:
|
|
322
470
|
with patch_stdout(True):
|
|
323
471
|
exp = await self.prompt_session.prompt_async(HTML('<style fg="cyan"><b>></b></style> '))
|
|
324
472
|
|
|
@@ -326,19 +474,18 @@ class JsShell(ABC):
|
|
|
326
474
|
return
|
|
327
475
|
|
|
328
476
|
result = await self.evaluate_expression(exp)
|
|
329
|
-
colorful_result = highlight(
|
|
330
|
-
|
|
331
|
-
|
|
477
|
+
colorful_result = highlight(
|
|
478
|
+
f"{result}", lexers.JavascriptLexer(), formatters.Terminal256Formatter(style="stata-dark")
|
|
479
|
+
)
|
|
480
|
+
print(colorful_result, end="")
|
|
332
481
|
|
|
333
|
-
async def start(self, url: str =
|
|
482
|
+
async def start(self, url: str = "") -> None:
|
|
334
483
|
if url:
|
|
335
484
|
await self.navigate(url)
|
|
336
485
|
while True:
|
|
337
486
|
try:
|
|
338
487
|
await self.js_iter()
|
|
339
|
-
except WirError as e:
|
|
340
|
-
logger.error(e)
|
|
341
|
-
except InspectorEvaluateError as e:
|
|
488
|
+
except (WirError, InspectorEvaluateError) as e:
|
|
342
489
|
logger.error(e)
|
|
343
490
|
except KeyboardInterrupt: # KeyboardInterrupt Control-C
|
|
344
491
|
pass
|
|
@@ -347,76 +494,91 @@ class JsShell(ABC):
|
|
|
347
494
|
|
|
348
495
|
@staticmethod
|
|
349
496
|
def webinspector_history_path() -> str:
|
|
350
|
-
return str(get_home_folder() /
|
|
497
|
+
return str(get_home_folder() / "webinspector_history")
|
|
351
498
|
|
|
352
499
|
|
|
353
500
|
class AutomationJsShell(JsShell):
|
|
354
|
-
def __init__(self, driver: WebDriver):
|
|
501
|
+
def __init__(self, driver: WebDriver) -> None:
|
|
355
502
|
super().__init__()
|
|
356
|
-
self.driver = driver
|
|
503
|
+
self.driver: WebDriver = driver
|
|
357
504
|
|
|
358
505
|
@classmethod
|
|
359
506
|
@asynccontextmanager
|
|
360
|
-
async def create(
|
|
361
|
-
|
|
362
|
-
|
|
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)
|
|
363
512
|
driver = WebDriver(automation_session)
|
|
364
|
-
driver.start_session()
|
|
513
|
+
await driver.start_session()
|
|
365
514
|
try:
|
|
366
515
|
yield cls(driver)
|
|
367
516
|
finally:
|
|
368
|
-
automation_session.stop_session()
|
|
369
|
-
inspector.close()
|
|
517
|
+
await automation_session.stop_session()
|
|
518
|
+
await inspector.close()
|
|
370
519
|
|
|
371
|
-
async def evaluate_expression(self, exp: str, return_by_value: bool = False):
|
|
372
|
-
return self.driver.execute_script(f
|
|
520
|
+
async def evaluate_expression(self, exp: str, return_by_value: bool = False) -> Any:
|
|
521
|
+
return await self.driver.execute_script(f"return {exp}")
|
|
373
522
|
|
|
374
|
-
async def navigate(self, url: str):
|
|
375
|
-
self.driver.get(url)
|
|
523
|
+
async def navigate(self, url: str) -> None:
|
|
524
|
+
await self.driver.get(url)
|
|
376
525
|
|
|
377
526
|
|
|
378
527
|
class InspectorJsShell(JsShell):
|
|
379
|
-
def __init__(self, inspector_session: InspectorSession):
|
|
528
|
+
def __init__(self, inspector_session: InspectorSession) -> None:
|
|
380
529
|
super().__init__()
|
|
381
|
-
self.inspector_session = inspector_session
|
|
530
|
+
self.inspector_session: InspectorSession = inspector_session
|
|
382
531
|
|
|
383
532
|
@classmethod
|
|
384
533
|
@asynccontextmanager
|
|
385
|
-
async def create(
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
534
|
+
async def create(
|
|
535
|
+
cls, lockdown: LockdownServiceProvider, timeout: float, open_safari: bool
|
|
536
|
+
) -> "AsyncIterator[InspectorJsShell]":
|
|
537
|
+
inspector = WebinspectorService(lockdown=lockdown)
|
|
538
|
+
await inspector.connect(timeout)
|
|
539
|
+
if open_safari:
|
|
540
|
+
_ = await inspector.open_app(SAFARI)
|
|
541
|
+
application_page = await cls.query_page(inspector, bundle_identifier=SAFARI if open_safari else None)
|
|
542
|
+
if application_page is None:
|
|
543
|
+
raise typer.Exit()
|
|
544
|
+
|
|
545
|
+
inspector_session = await inspector.inspector_session(application_page.application, application_page.page)
|
|
392
546
|
await inspector_session.console_enable()
|
|
393
547
|
await inspector_session.runtime_enable()
|
|
394
548
|
|
|
395
549
|
try:
|
|
396
550
|
yield cls(inspector_session)
|
|
397
551
|
finally:
|
|
398
|
-
inspector.close()
|
|
552
|
+
await inspector.close()
|
|
399
553
|
|
|
400
|
-
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:
|
|
401
555
|
return await self.inspector_session.runtime_evaluate(exp, return_by_value=return_by_value)
|
|
402
556
|
|
|
403
557
|
async def navigate(self, url: str):
|
|
404
558
|
await self.inspector_session.navigate_to_url(url)
|
|
405
559
|
|
|
406
560
|
@staticmethod
|
|
407
|
-
def query_page(
|
|
408
|
-
|
|
409
|
-
|
|
561
|
+
async def query_page(
|
|
562
|
+
inspector: WebinspectorService, bundle_identifier: Optional[str] = None
|
|
563
|
+
) -> Optional[ApplicationPage]:
|
|
564
|
+
available_pages = await inspector.get_open_application_pages(timeout=1)
|
|
565
|
+
if bundle_identifier is not None:
|
|
566
|
+
available_pages = [
|
|
567
|
+
application_page
|
|
568
|
+
for application_page in available_pages
|
|
569
|
+
if application_page.application.bundle == bundle_identifier
|
|
570
|
+
]
|
|
410
571
|
if not available_pages:
|
|
411
|
-
logger.error(
|
|
412
|
-
return
|
|
572
|
+
logger.error("Unable to find available pages (try to unlock device)")
|
|
573
|
+
return None
|
|
413
574
|
|
|
414
|
-
page_query = [inquirer3.List(
|
|
415
|
-
page = inquirer3.prompt(page_query, theme=GreenPassion(), raise_keyboard_interrupt=True)[
|
|
575
|
+
page_query = [inquirer3.List("page", message="choose page", choices=available_pages, carousel=True)]
|
|
576
|
+
page = inquirer3.prompt(page_query, theme=GreenPassion(), raise_keyboard_interrupt=True)["page"]
|
|
416
577
|
return page
|
|
417
578
|
|
|
418
579
|
|
|
419
|
-
async def run_js_shell(
|
|
420
|
-
|
|
421
|
-
|
|
580
|
+
async def run_js_shell(
|
|
581
|
+
js_shell_class: type[JsShell], lockdown: LockdownServiceProvider, timeout: float, url: str, open_safari: bool
|
|
582
|
+
) -> None:
|
|
583
|
+
async with js_shell_class.create(lockdown, timeout, open_safari) as js_shell_instance:
|
|
422
584
|
await js_shell_instance.start(url)
|