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.
Files changed (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import json
3
4
  import logging
4
5
  import uuid
@@ -6,10 +7,11 @@ from dataclasses import dataclass, fields
6
7
  from enum import Enum
7
8
  from typing import Optional, Union
8
9
 
9
- import nest_asyncio
10
-
11
- from pymobiledevice3.exceptions import LaunchingApplicationError, RemoteAutomationNotEnabledError, \
12
- WebInspectorNotEnabledError
10
+ from pymobiledevice3.exceptions import (
11
+ LaunchingApplicationError,
12
+ RemoteAutomationNotEnabledError,
13
+ WebInspectorNotEnabledError,
14
+ )
13
15
  from pymobiledevice3.lockdown import LockdownClient
14
16
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
15
17
  from pymobiledevice3.service_connection import ServiceConnection
@@ -17,55 +19,55 @@ from pymobiledevice3.services.web_protocol.automation_session import AutomationS
17
19
  from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
18
20
  from pymobiledevice3.services.web_protocol.session_protocol import SessionProtocol
19
21
 
20
- SAFARI = 'com.apple.mobilesafari'
22
+ SAFARI = "com.apple.mobilesafari"
21
23
 
22
24
 
23
25
  def key_to_pid(key: str) -> int:
24
- return int(key.split(':')[1])
26
+ return int(key.split(":")[1])
25
27
 
26
28
 
27
29
  class WirTypes(Enum):
28
- AUTOMATION = 'WIRTypeAutomation'
29
- ITML = 'WIRTypeITML'
30
- JAVASCRIPT = 'WIRTypeJavaScript'
31
- PAGE = 'WIRTypePage'
32
- SERVICE_WORKER = 'WIRTypeServiceWorker'
33
- WEB = 'WIRTypeWeb'
34
- WEB_PAGE = 'WIRTypeWebPage'
35
- AUTOMATICALLY_PAUSE = 'WIRAutomaticallyPause'
30
+ AUTOMATION = "WIRTypeAutomation"
31
+ ITML = "WIRTypeITML"
32
+ JAVASCRIPT = "WIRTypeJavaScript"
33
+ PAGE = "WIRTypePage"
34
+ SERVICE_WORKER = "WIRTypeServiceWorker"
35
+ WEB = "WIRTypeWeb"
36
+ WEB_PAGE = "WIRTypeWebPage"
37
+ AUTOMATICALLY_PAUSE = "WIRAutomaticallyPause"
36
38
 
37
39
 
38
40
  class AutomationAvailability(Enum):
39
- NOT_AVAILABLE = 'WIRAutomationAvailabilityNotAvailable'
40
- AVAILABLE = 'WIRAutomationAvailabilityAvailable'
41
- UNKNOWN = 'WIRAutomationAvailabilityUnknown'
41
+ NOT_AVAILABLE = "WIRAutomationAvailabilityNotAvailable"
42
+ AVAILABLE = "WIRAutomationAvailabilityAvailable"
43
+ UNKNOWN = "WIRAutomationAvailabilityUnknown"
42
44
 
43
45
 
44
46
  @dataclass
45
47
  class Page:
46
48
  id_: int
47
49
  type_: WirTypes
48
- web_url: str = ''
49
- web_title: str = ''
50
+ web_url: str = ""
51
+ web_title: str = ""
50
52
  automation_is_paired_key: bool = False
51
- automation_name: str = ''
52
- automation_version: str = ''
53
- automation_session_id: str = ''
54
- automation_connection_id: str = ''
53
+ automation_name: str = ""
54
+ automation_version: str = ""
55
+ automation_session_id: str = ""
56
+ automation_connection_id: str = ""
55
57
 
56
58
  @classmethod
57
- def from_page_dictionary(cls, page_dict: dict) -> 'Page':
58
- p = cls(page_dict['WIRPageIdentifierKey'], WirTypes(page_dict['WIRTypeKey']))
59
+ def from_page_dictionary(cls, page_dict: dict) -> "Page":
60
+ p = cls(page_dict["WIRPageIdentifierKey"], WirTypes(page_dict["WIRTypeKey"]))
59
61
  if p.type_ in (WirTypes.WEB, WirTypes.WEB_PAGE):
60
- p.web_title = page_dict['WIRTitleKey']
61
- p.web_url = page_dict['WIRURLKey']
62
+ p.web_title = page_dict["WIRTitleKey"]
63
+ p.web_url = page_dict["WIRURLKey"]
62
64
  if p.type_ == WirTypes.AUTOMATION:
63
- p.automation_is_paired_key = page_dict['WIRAutomationTargetIsPairedKey']
64
- p.automation_name = page_dict['WIRAutomationTargetNameKey']
65
- p.automation_version = page_dict['WIRAutomationTargetVersionKey']
66
- p.automation_session_id = page_dict['WIRSessionIdentifierKey']
67
- if 'WIRConnectionIdentifierKey' in page_dict:
68
- p.automation_connection_id = page_dict['WIRConnectionIdentifierKey']
65
+ p.automation_is_paired_key = page_dict["WIRAutomationTargetIsPairedKey"]
66
+ p.automation_name = page_dict["WIRAutomationTargetNameKey"]
67
+ p.automation_version = page_dict["WIRAutomationTargetVersionKey"]
68
+ p.automation_session_id = page_dict["WIRSessionIdentifierKey"]
69
+ if "WIRConnectionIdentifierKey" in page_dict:
70
+ p.automation_connection_id = page_dict["WIRConnectionIdentifierKey"]
69
71
  return p
70
72
 
71
73
  def update(self, page_dict: dict):
@@ -74,7 +76,7 @@ class Page:
74
76
  setattr(self, field.name, getattr(new_p, field.name))
75
77
 
76
78
  def __str__(self):
77
- return f'id: {self.id_}, title: {self.web_title}, url: {self.web_url}'
79
+ return f"id: {self.id_}, title: {self.web_title}, url: {self.web_url}"
78
80
 
79
81
 
80
82
  @dataclass
@@ -87,42 +89,42 @@ class Application:
87
89
  active: int
88
90
  proxy: bool
89
91
  ready: bool
90
- host: str = ''
92
+ host: str = ""
91
93
 
92
94
  @classmethod
93
- def from_application_dictionary(cls, app_dict) -> 'Application':
95
+ def from_application_dictionary(cls, app_dict) -> "Application":
94
96
  return cls(
95
- app_dict['WIRApplicationIdentifierKey'],
96
- app_dict['WIRApplicationBundleIdentifierKey'],
97
- key_to_pid(app_dict['WIRApplicationIdentifierKey']),
98
- app_dict['WIRApplicationNameKey'],
99
- AutomationAvailability(app_dict['WIRAutomationAvailabilityKey']),
100
- app_dict['WIRIsApplicationActiveKey'],
101
- app_dict['WIRIsApplicationProxyKey'],
102
- app_dict['WIRIsApplicationReadyKey'],
103
- app_dict.get('WIRHostApplicationIdentifierKey', ''),
97
+ app_dict["WIRApplicationIdentifierKey"],
98
+ app_dict["WIRApplicationBundleIdentifierKey"],
99
+ key_to_pid(app_dict["WIRApplicationIdentifierKey"]),
100
+ app_dict["WIRApplicationNameKey"],
101
+ AutomationAvailability(app_dict["WIRAutomationAvailabilityKey"]),
102
+ app_dict["WIRIsApplicationActiveKey"],
103
+ app_dict["WIRIsApplicationProxyKey"],
104
+ app_dict["WIRIsApplicationReadyKey"],
105
+ app_dict.get("WIRHostApplicationIdentifierKey", ""),
104
106
  )
105
107
 
106
108
 
107
- class WebinspectorService:
108
- SERVICE_NAME = 'com.apple.webinspector'
109
- RSD_SERVICE_NAME = 'com.apple.webinspector.shim.remote'
109
+ @dataclass
110
+ class ApplicationPage:
111
+ application: Application
112
+ page: Page
110
113
 
111
- def __init__(self, lockdown: LockdownServiceProvider, loop=None):
112
- if loop is None:
113
- try:
114
- loop = asyncio.get_running_loop()
115
- except RuntimeError:
116
- loop = asyncio.new_event_loop()
117
- asyncio.set_event_loop(loop)
118
- nest_asyncio.apply(loop)
114
+ def __str__(self) -> str:
115
+ return f"<{self.application.name}({self.application.pid}) TYPE:{self.page.type_.value} URL:{self.page.web_url}>"
116
+
117
+
118
+ class WebinspectorService:
119
+ SERVICE_NAME = "com.apple.webinspector"
120
+ RSD_SERVICE_NAME = "com.apple.webinspector.shim.remote"
119
121
 
122
+ def __init__(self, lockdown: LockdownServiceProvider):
120
123
  if isinstance(lockdown, LockdownClient):
121
124
  self.service_name = self.SERVICE_NAME
122
125
  else:
123
126
  self.service_name = self.RSD_SERVICE_NAME
124
127
 
125
- self.loop = loop
126
128
  self.logger = logging.getLogger(__name__)
127
129
  self.lockdown = lockdown
128
130
  self.service: Optional[ServiceConnection] = None
@@ -133,33 +135,31 @@ class WebinspectorService:
133
135
  self.wir_message_results = {}
134
136
  self.wir_events = []
135
137
  self.receive_handlers = {
136
- '_rpc_reportCurrentState:': self._handle_report_current_state,
137
- '_rpc_reportConnectedApplicationList:': self._handle_report_connected_application_list,
138
- '_rpc_reportConnectedDriverList:': self._handle_report_connected_driver_list,
139
- '_rpc_applicationSentListing:': self._handle_application_sent_listing,
140
- '_rpc_applicationUpdated:': self._handle_application_updated,
141
- '_rpc_applicationConnected:': self._handle_application_connected,
142
- '_rpc_applicationSentData:': self._handle_application_sent_data,
143
- '_rpc_applicationDisconnected:': self._handle_application_disconnected,
138
+ "_rpc_reportCurrentState:": self._handle_report_current_state,
139
+ "_rpc_reportConnectedApplicationList:": self._handle_report_connected_application_list,
140
+ "_rpc_reportConnectedDriverList:": self._handle_report_connected_driver_list,
141
+ "_rpc_applicationSentListing:": self._handle_application_sent_listing,
142
+ "_rpc_applicationUpdated:": self._handle_application_updated,
143
+ "_rpc_applicationConnected:": self._handle_application_connected,
144
+ "_rpc_applicationSentData:": self._handle_application_sent_data,
145
+ "_rpc_applicationDisconnected:": self._handle_application_disconnected,
144
146
  }
145
147
  self._recv_task: Optional[asyncio.Task] = None
146
148
 
147
- def connect(self, timeout: Union[float, int] = None):
148
- self.service = self.await_(self.lockdown.aio_start_lockdown_service(self.service_name))
149
- self.await_(self._report_identifier())
149
+ async def connect(self, timeout: Optional[Union[float, int]] = None):
150
+ self.service = await self.lockdown.aio_start_lockdown_service(self.service_name)
151
+ await self._report_identifier()
150
152
  try:
151
- self._handle_recv(self.await_(asyncio.wait_for(self._recv_message(), timeout)))
153
+ await self._handle_recv(await asyncio.wait_for(self._recv_message(), timeout))
152
154
  except asyncio.TimeoutError as e:
153
155
  raise WebInspectorNotEnabledError from e
154
- self._recv_task = self.loop.create_task(self._receiving_task())
156
+ self._recv_task = asyncio.create_task(self._receiving_task())
155
157
 
156
- def close(self):
158
+ async def close(self):
157
159
  self._recv_task.cancel()
158
- try:
159
- self.await_(self._recv_task)
160
- except asyncio.CancelledError:
161
- pass
162
- self.await_(self.service.aio_close())
160
+ with contextlib.suppress(asyncio.CancelledError):
161
+ await self._recv_task
162
+ await self.service.aio_close()
163
163
 
164
164
  async def _recv_message(self):
165
165
  while True:
@@ -170,41 +170,57 @@ class WebinspectorService:
170
170
 
171
171
  async def _receiving_task(self):
172
172
  while True:
173
- self._handle_recv(await self._recv_message())
173
+ await self._handle_recv(await self._recv_message())
174
174
 
175
- def automation_session(self, app: Application) -> AutomationSession:
176
- if self.state == 'WIRAutomationAvailabilityNotAvailable':
175
+ async def automation_session(self, app: Application) -> AutomationSession:
176
+ if self.state == "WIRAutomationAvailabilityNotAvailable":
177
177
  raise RemoteAutomationNotEnabledError()
178
178
  session_id = str(uuid.uuid4()).upper()
179
- self.await_(self._forward_automation_session_request(session_id, app.id_))
180
- self.await_(self._forward_get_listing(app.id_))
181
- page = self.await_(self._wait_for_page(session_id))
182
- self.await_(self._forward_socket_setup(session_id, app.id_, page.id_))
183
- self.await_(self._forward_get_listing(app.id_))
179
+ await self._forward_automation_session_request(session_id, app.id_)
180
+ await self._forward_get_listing(app.id_)
181
+ page = await self._wait_for_page(session_id)
182
+ await self._forward_socket_setup(session_id, app.id_, page.id_)
183
+ await self._forward_get_listing(app.id_)
184
184
  while not page.automation_connection_id:
185
- self.await_(asyncio.sleep(0))
185
+ await asyncio.sleep(0)
186
186
  return AutomationSession(SessionProtocol(self, session_id, app, page))
187
187
 
188
- async def inspector_session(self, app: Application, page: Page, wait_target: bool = True) -> InspectorSession:
188
+ async def inspector_session(self, app: Application, page: Page) -> InspectorSession:
189
189
  session_id = str(uuid.uuid4()).upper()
190
- return await InspectorSession.create(SessionProtocol(self, session_id, app, page, method_prefix=''),
191
- wait_target=wait_target)
190
+ return await InspectorSession.create(
191
+ SessionProtocol(self, session_id, app, page, method_prefix=""),
192
+ wait_target=page.type_ != WirTypes.JAVASCRIPT,
193
+ )
192
194
 
193
- def get_open_pages(self) -> dict:
195
+ async def get_open_pages(self) -> dict:
194
196
  apps = {}
195
- self.await_(asyncio.gather(*[self._forward_get_listing(app) for app in self.connected_application]))
197
+ await asyncio.gather(*[self._forward_get_listing(app) for app in self.connected_application])
196
198
  for app in self.connected_application:
197
199
  if self.application_pages.get(app, False):
198
200
  apps[self.connected_application[app].name] = self.application_pages[app].values()
199
201
  return apps
200
202
 
201
- def open_app(self, bundle: str, timeout: Union[float, int] = 3) -> Application:
202
- self.await_(self._request_application_launch(bundle))
203
- self.get_open_pages()
203
+ async def get_open_application_pages(self, timeout: float) -> list[ApplicationPage]:
204
+ # Query all connected applications
205
+ await self._get_connected_applications()
206
+
207
+ # Give some time for `webinspectord` to reply with all inspectable applications
208
+ await asyncio.sleep(timeout)
209
+
210
+ result = []
211
+ for app in self.connected_application:
212
+ if self.application_pages.get(app, False):
213
+ for page in self.application_pages[app].values():
214
+ result.append(ApplicationPage(self.connected_application[app], page))
215
+ return result
216
+
217
+ async def open_app(self, bundle: str, timeout: Union[float, int] = 3) -> Application:
218
+ await self._request_application_launch(bundle)
219
+ await self.get_open_pages()
204
220
  try:
205
- return self.await_(asyncio.wait_for(self._wait_for_application(bundle), timeout=timeout))
206
- except TimeoutError:
207
- raise LaunchingApplicationError()
221
+ return await asyncio.wait_for(self._wait_for_application(bundle), timeout)
222
+ except TimeoutError as e:
223
+ raise LaunchingApplicationError() from e
208
224
 
209
225
  async def send_socket_data(self, session_id: str, app_id: str, page_id: int, data: dict):
210
226
  await self._forward_socket_data(session_id, app_id, page_id, data)
@@ -217,116 +233,134 @@ class WebinspectorService:
217
233
  for page in self.application_pages[app_id]:
218
234
  if page == page_id:
219
235
  return self.connected_application[app_id], self.application_pages[app_id][page_id]
236
+ raise KeyError(f"Page with id {page_id} not found")
220
237
 
221
- def flush_input(self, duration: Union[float, int] = 0):
222
- return self.await_(asyncio.sleep(duration))
238
+ async def flush_input(self, duration: Union[float, int] = 0):
239
+ return await asyncio.sleep(duration)
223
240
 
224
- def await_(self, awaitable):
225
- return self.loop.run_until_complete(asyncio.ensure_future(awaitable, loop=self.loop))
241
+ async def _handle_recv(self, plist):
242
+ await self.receive_handlers[plist["__selector"]](plist["__argument"])
226
243
 
227
- def _handle_recv(self, plist):
228
- self.receive_handlers[plist['__selector']](plist['__argument'])
244
+ async def _handle_report_current_state(self, arg):
245
+ self.state = arg["WIRAutomationAvailabilityKey"]
229
246
 
230
- def _handle_report_current_state(self, arg):
231
- self.state = arg['WIRAutomationAvailabilityKey']
232
-
233
- def _handle_report_connected_application_list(self, arg):
247
+ async def _handle_report_connected_application_list(self, arg):
234
248
  self.connected_application = {}
235
- for key, application in arg['WIRApplicationDictionaryKey'].items():
249
+ for key, application in arg["WIRApplicationDictionaryKey"].items():
236
250
  self.connected_application[key] = Application.from_application_dictionary(application)
237
251
 
238
- def _handle_report_connected_driver_list(self, arg):
252
+ # Immediately also query the application pages
253
+ await self._forward_get_listing(self.connected_application[key].id_)
254
+
255
+ async def _handle_report_connected_driver_list(self, arg):
239
256
  pass
240
257
 
241
- def _handle_application_sent_listing(self, arg):
242
- if arg['WIRApplicationIdentifierKey'] in self.application_pages:
243
- for id_, page in arg['WIRListingKey'].items():
244
- if id_ in self.application_pages[arg['WIRApplicationIdentifierKey']]:
245
- self.application_pages[arg['WIRApplicationIdentifierKey']][id_].update(page)
258
+ async def _handle_application_sent_listing(self, arg):
259
+ if arg["WIRApplicationIdentifierKey"] in self.application_pages:
260
+ # Update existing application pages
261
+ for id_, page in arg["WIRListingKey"].items():
262
+ if id_ in self.application_pages[arg["WIRApplicationIdentifierKey"]]:
263
+ self.application_pages[arg["WIRApplicationIdentifierKey"]][id_].update(page)
246
264
  else:
247
- self.application_pages[arg['WIRApplicationIdentifierKey']][id_] = Page.from_page_dictionary(page)
265
+ self.application_pages[arg["WIRApplicationIdentifierKey"]][id_] = Page.from_page_dictionary(page)
248
266
  else:
267
+ # Add new application pages
249
268
  pages = {}
250
- for id_, page in arg['WIRListingKey'].items():
269
+ for id_, page in arg["WIRListingKey"].items():
251
270
  pages[id_] = Page.from_page_dictionary(page)
252
- self.application_pages[arg['WIRApplicationIdentifierKey']] = pages
271
+ self.application_pages[arg["WIRApplicationIdentifierKey"]] = pages
253
272
 
254
- def _handle_application_updated(self, arg):
273
+ async def _handle_application_updated(self, arg):
255
274
  app = Application.from_application_dictionary(arg)
256
275
  self.connected_application[app.id_] = app
257
276
 
258
- def _handle_application_connected(self, arg):
277
+ async def _handle_application_connected(self, arg):
259
278
  app = Application.from_application_dictionary(arg)
260
279
  self.connected_application[app.id_] = app
261
280
 
262
- def _handle_application_sent_data(self, arg):
263
- response = json.loads(arg['WIRMessageDataKey'])
281
+ async def _handle_application_sent_data(self, arg):
282
+ response = json.loads(arg["WIRMessageDataKey"])
264
283
 
265
- if 'id' in response:
266
- self.wir_message_results[response['id']] = response
284
+ if "id" in response:
285
+ self.wir_message_results[response["id"]] = response
267
286
  else:
268
287
  self.wir_events.append(response)
269
288
 
270
- def _handle_application_disconnected(self, arg):
271
- self.connected_application.pop(arg['WIRApplicationIdentifierKey'], None)
272
- self.application_pages.pop(arg['WIRApplicationIdentifierKey'], None)
289
+ async def _handle_application_disconnected(self, arg):
290
+ self.connected_application.pop(arg["WIRApplicationIdentifierKey"], None)
291
+ self.application_pages.pop(arg["WIRApplicationIdentifierKey"], None)
273
292
 
274
293
  async def _report_identifier(self):
275
- await self._send_message('_rpc_reportIdentifier:')
294
+ await self._send_message("_rpc_reportIdentifier:")
276
295
 
277
296
  async def _forward_get_listing(self, app_id):
278
- self.logger.debug(f'Listing app with id {app_id}')
279
- await self._send_message('_rpc_forwardGetListing:', {'WIRApplicationIdentifierKey': app_id})
297
+ self.logger.debug(f"Listing app with id {app_id}")
298
+ await self._send_message("_rpc_forwardGetListing:", {"WIRApplicationIdentifierKey": app_id})
280
299
 
281
300
  async def _request_application_launch(self, bundle: str):
282
- await self._send_message('_rpc_requestApplicationLaunch:', {'WIRApplicationBundleIdentifierKey': bundle})
301
+ await self._send_message("_rpc_requestApplicationLaunch:", {"WIRApplicationBundleIdentifierKey": bundle})
302
+
303
+ async def _get_connected_applications(self) -> None:
304
+ await self._send_message("_rpc_getConnectedApplications:", {})
283
305
 
284
306
  async def _forward_automation_session_request(self, session_id: str, app_id: str):
285
- await self._send_message('_rpc_forwardAutomationSessionRequest:', {
286
- 'WIRApplicationIdentifierKey': app_id,
287
- 'WIRSessionCapabilitiesKey': {
288
- 'org.webkit.webdriver.webrtc.allow-insecure-media-capture': True,
289
- 'org.webkit.webdriver.webrtc.suppress-ice-candidate-filtering': False,
307
+ await self._send_message(
308
+ "_rpc_forwardAutomationSessionRequest:",
309
+ {
310
+ "WIRApplicationIdentifierKey": app_id,
311
+ "WIRSessionCapabilitiesKey": {
312
+ "org.webkit.webdriver.webrtc.allow-insecure-media-capture": True,
313
+ "org.webkit.webdriver.webrtc.suppress-ice-candidate-filtering": False,
314
+ },
315
+ "WIRSessionIdentifierKey": session_id,
290
316
  },
291
- 'WIRSessionIdentifierKey': session_id
292
- })
317
+ )
293
318
 
294
319
  async def _forward_socket_setup(self, session_id: str, app_id: str, page_id: int, pause: bool = True):
295
320
  message = {
296
- 'WIRApplicationIdentifierKey': app_id,
297
- 'WIRPageIdentifierKey': page_id,
298
- 'WIRSenderKey': session_id
321
+ "WIRApplicationIdentifierKey": app_id,
322
+ "WIRPageIdentifierKey": page_id,
323
+ "WIRSenderKey": session_id,
324
+ "WIRMessageDataTypeChunkSupportedKey": 0,
299
325
  }
300
326
  if not pause:
301
- message['WIRAutomaticallyPause'] = False
302
- await self._send_message('_rpc_forwardSocketSetup:', message)
327
+ message["WIRAutomaticallyPause"] = False
328
+ await self._send_message("_rpc_forwardSocketSetup:", message)
303
329
 
304
330
  async def _forward_socket_data(self, session_id: str, app_id: str, page_id: int, data: dict):
305
- await self._send_message('_rpc_forwardSocketData:', {
306
- 'WIRApplicationIdentifierKey': app_id,
307
- 'WIRPageIdentifierKey': page_id,
308
- 'WIRSessionIdentifierKey': session_id,
309
- 'WIRSocketDataKey': json.dumps(data).encode(),
310
- })
331
+ await self._send_message(
332
+ "_rpc_forwardSocketData:",
333
+ {
334
+ "WIRApplicationIdentifierKey": app_id,
335
+ "WIRPageIdentifierKey": page_id,
336
+ "WIRSessionIdentifierKey": session_id,
337
+ "WIRSenderKey": session_id,
338
+ "WIRSocketDataKey": json.dumps(data).encode(),
339
+ },
340
+ )
311
341
 
312
342
  async def _forward_indicate_web_view(self, app_id: str, page_id: int, enable: bool):
313
- await self._send_message('_rpc_forwardIndicateWebView'), {
314
- 'WIRApplicationIdentifierKey': app_id,
315
- 'WIRPageIdentifierKey': page_id,
316
- 'WIRIndicateEnabledKey': enable,
317
- }
343
+ (
344
+ await self._send_message("_rpc_forwardIndicateWebView"),
345
+ {
346
+ "WIRApplicationIdentifierKey": app_id,
347
+ "WIRPageIdentifierKey": page_id,
348
+ "WIRIndicateEnabledKey": enable,
349
+ },
350
+ )
318
351
 
319
352
  async def _send_message(self, selector: str, args=None):
320
353
  if args is None:
321
354
  args = {}
322
- args['WIRConnectionIdentifierKey'] = self.connection_id
323
- await self.service.aio_send_plist({'__selector': selector, '__argument': args})
355
+ args["WIRConnectionIdentifierKey"] = self.connection_id
356
+ await self.service.aio_send_plist({"__selector": selector, "__argument": args})
324
357
 
325
358
  def _page_by_automation_session(self, session_id: str) -> Page:
326
359
  for app_id in self.application_pages:
327
360
  for page in self.application_pages[app_id]:
328
361
  if page.type_ == WirTypes.AUTOMATION and page.automation_session_id == session_id:
329
362
  return page
363
+ raise KeyError(f"Automation session with id {session_id} not found")
330
364
 
331
365
  async def _wait_for_page(self, session_id: str):
332
366
  while True:
@@ -336,7 +370,7 @@ class WebinspectorService:
336
370
  return page
337
371
  await asyncio.sleep(0)
338
372
 
339
- async def _wait_for_application(self, bundle: str = '', app_id: str = '') -> Application:
373
+ async def _wait_for_application(self, bundle: str = "", app_id: str = "") -> Application:
340
374
  while True:
341
375
  for app in self.connected_application.values():
342
376
  if bundle and app.bundle == bundle: