execbro 2.3.0 → 2.5.0

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 (549) hide show
  1. package/build/__tests__/helpers/fake-cdp-server.d.ts +0 -12
  2. package/build/__tests__/helpers/fake-cdp-server.d.ts.map +1 -1
  3. package/build/__tests__/helpers/fake-cdp-server.js +0 -22
  4. package/build/__tests__/helpers/fake-cdp-server.js.map +1 -1
  5. package/build/__tests__/helpers/tap-test-helpers.d.ts +0 -23
  6. package/build/__tests__/helpers/tap-test-helpers.d.ts.map +1 -1
  7. package/build/__tests__/helpers/tap-test-helpers.js +2 -29
  8. package/build/__tests__/helpers/tap-test-helpers.js.map +1 -1
  9. package/build/__tests__/integration/auto-reconnect.test.js +4 -14
  10. package/build/__tests__/integration/auto-reconnect.test.js.map +1 -1
  11. package/build/__tests__/integration/connection-health.test.js +1 -10
  12. package/build/__tests__/integration/connection-health.test.js.map +1 -1
  13. package/build/__tests__/integration/execute-in-app.test.js +1 -11
  14. package/build/__tests__/integration/execute-in-app.test.js.map +1 -1
  15. package/build/__tests__/integration/native-logs.test.js +0 -5
  16. package/build/__tests__/integration/native-logs.test.js.map +1 -1
  17. package/build/__tests__/integration/per-call-timeout.test.js +1 -5
  18. package/build/__tests__/integration/per-call-timeout.test.js.map +1 -1
  19. package/build/__tests__/integration/socket-close-midflight.test.js +2 -12
  20. package/build/__tests__/integration/socket-close-midflight.test.js.map +1 -1
  21. package/build/__tests__/integration/stale-target-probe.test.js +2 -17
  22. package/build/__tests__/integration/stale-target-probe.test.js.map +1 -1
  23. package/build/__tests__/integration/tap-strategies.test.js +2 -36
  24. package/build/__tests__/integration/tap-strategies.test.js.map +1 -1
  25. package/build/__tests__/integration/text-input.test.js +0 -39
  26. package/build/__tests__/integration/text-input.test.js.map +1 -1
  27. package/build/__tests__/integration/tools.test.js +1 -7
  28. package/build/__tests__/integration/tools.test.js.map +1 -1
  29. package/build/__tests__/setup.js +0 -2
  30. package/build/__tests__/setup.js.map +1 -1
  31. package/build/__tests__/unit/androidEmulatorAvds.test.js +0 -10
  32. package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -1
  33. package/build/__tests__/unit/appContext.test.js +0 -2
  34. package/build/__tests__/unit/appContext.test.js.map +1 -1
  35. package/build/__tests__/unit/appRequest.test.js +0 -3
  36. package/build/__tests__/unit/appRequest.test.js.map +1 -1
  37. package/build/__tests__/unit/buildToken.test.js +0 -6
  38. package/build/__tests__/unit/buildToken.test.js.map +1 -1
  39. package/build/__tests__/unit/burst.test.js +0 -1
  40. package/build/__tests__/unit/burst.test.js.map +1 -1
  41. package/build/__tests__/unit/clearLogsNative.test.js +0 -8
  42. package/build/__tests__/unit/clearLogsNative.test.js.map +1 -1
  43. package/build/__tests__/unit/componentFormatNaming.test.js +0 -13
  44. package/build/__tests__/unit/componentFormatNaming.test.js.map +1 -1
  45. package/build/__tests__/unit/componentSource.test.js +0 -4
  46. package/build/__tests__/unit/componentSource.test.js.map +1 -1
  47. package/build/__tests__/unit/componentSourceResolve.test.js +0 -1
  48. package/build/__tests__/unit/componentSourceResolve.test.js.map +1 -1
  49. package/build/__tests__/unit/contextWiring.test.js +0 -3
  50. package/build/__tests__/unit/contextWiring.test.js.map +1 -1
  51. package/build/__tests__/unit/coordinateMiss.test.js +0 -16
  52. package/build/__tests__/unit/coordinateMiss.test.js.map +1 -1
  53. package/build/__tests__/unit/deviceDiscovery.test.js +0 -4
  54. package/build/__tests__/unit/deviceDiscovery.test.js.map +1 -1
  55. package/build/__tests__/unit/deviceResolution.test.js +0 -2
  56. package/build/__tests__/unit/deviceResolution.test.js.map +1 -1
  57. package/build/__tests__/unit/deviceResolver.test.js +0 -1
  58. package/build/__tests__/unit/deviceResolver.test.js.map +1 -1
  59. package/build/__tests__/unit/deviceResolverStaleRetry.test.js +0 -8
  60. package/build/__tests__/unit/deviceResolverStaleRetry.test.js.map +1 -1
  61. package/build/__tests__/unit/epochRender.test.js +0 -3
  62. package/build/__tests__/unit/epochRender.test.js.map +1 -1
  63. package/build/__tests__/unit/errorCategorization.test.js +0 -18
  64. package/build/__tests__/unit/errorCategorization.test.js.map +1 -1
  65. package/build/__tests__/unit/exec.test.js +0 -1
  66. package/build/__tests__/unit/exec.test.js.map +1 -1
  67. package/build/__tests__/unit/executor.test.js +0 -15
  68. package/build/__tests__/unit/executor.test.js.map +1 -1
  69. package/build/__tests__/unit/findElementTreeReuse.test.js +0 -12
  70. package/build/__tests__/unit/findElementTreeReuse.test.js.map +1 -1
  71. package/build/__tests__/unit/fingerprint.test.js +0 -4
  72. package/build/__tests__/unit/fingerprint.test.js.map +1 -1
  73. package/build/__tests__/unit/focusedInputTools.test.js +0 -4
  74. package/build/__tests__/unit/focusedInputTools.test.js.map +1 -1
  75. package/build/__tests__/unit/getLogsEmptyContract.test.js +0 -30
  76. package/build/__tests__/unit/getLogsEmptyContract.test.js.map +1 -1
  77. package/build/__tests__/unit/getLogsNativeSource.test.js +0 -22
  78. package/build/__tests__/unit/getLogsNativeSource.test.js.map +1 -1
  79. package/build/__tests__/unit/graphqlOperation.test.d.ts +2 -0
  80. package/build/__tests__/unit/graphqlOperation.test.d.ts.map +1 -0
  81. package/build/__tests__/unit/graphqlOperation.test.js +107 -0
  82. package/build/__tests__/unit/graphqlOperation.test.js.map +1 -0
  83. package/build/__tests__/unit/inputTarget.test.js +0 -16
  84. package/build/__tests__/unit/inputTarget.test.js.map +1 -1
  85. package/build/__tests__/unit/inputTextTool.test.js +0 -3
  86. package/build/__tests__/unit/inputTextTool.test.js.map +1 -1
  87. package/build/__tests__/unit/jsLogEvents.test.js +0 -3
  88. package/build/__tests__/unit/jsLogEvents.test.js.map +1 -1
  89. package/build/__tests__/unit/keyboardMetrics.test.js +0 -2
  90. package/build/__tests__/unit/keyboardMetrics.test.js.map +1 -1
  91. package/build/__tests__/unit/keyboardRaise.test.js +0 -2
  92. package/build/__tests__/unit/keyboardRaise.test.js.map +1 -1
  93. package/build/__tests__/unit/licenseGrace.test.js +1 -4
  94. package/build/__tests__/unit/licenseGrace.test.js.map +1 -1
  95. package/build/__tests__/unit/logEventFormat.test.js +0 -27
  96. package/build/__tests__/unit/logEventFormat.test.js.map +1 -1
  97. package/build/__tests__/unit/logEvents.test.js +1 -5
  98. package/build/__tests__/unit/logEvents.test.js.map +1 -1
  99. package/build/__tests__/unit/logGrouping.test.js +0 -2
  100. package/build/__tests__/unit/logGrouping.test.js.map +1 -1
  101. package/build/__tests__/unit/logOwnership.test.js +0 -11
  102. package/build/__tests__/unit/logOwnership.test.js.map +1 -1
  103. package/build/__tests__/unit/logRelevance.test.js +0 -6
  104. package/build/__tests__/unit/logRelevance.test.js.map +1 -1
  105. package/build/__tests__/unit/logSourceAndroid.test.js +0 -4
  106. package/build/__tests__/unit/logSourceAndroid.test.js.map +1 -1
  107. package/build/__tests__/unit/logSourceIos.test.js +0 -5
  108. package/build/__tests__/unit/logSourceIos.test.js.map +1 -1
  109. package/build/__tests__/unit/logs.test.js +0 -1
  110. package/build/__tests__/unit/logs.test.js.map +1 -1
  111. package/build/__tests__/unit/measureComponentTools.test.js +0 -1
  112. package/build/__tests__/unit/measureComponentTools.test.js.map +1 -1
  113. package/build/__tests__/unit/metro.test.js +0 -2
  114. package/build/__tests__/unit/metro.test.js.map +1 -1
  115. package/build/__tests__/unit/nativeInputValue.test.js +0 -4
  116. package/build/__tests__/unit/nativeInputValue.test.js.map +1 -1
  117. package/build/__tests__/unit/nativeLogs.test.js +1 -23
  118. package/build/__tests__/unit/nativeLogs.test.js.map +1 -1
  119. package/build/__tests__/unit/navigateVerify.test.js +0 -6
  120. package/build/__tests__/unit/navigateVerify.test.js.map +1 -1
  121. package/build/__tests__/unit/navigation.test.js +0 -5
  122. package/build/__tests__/unit/navigation.test.js.map +1 -1
  123. package/build/__tests__/unit/networkInterceptor.test.js +0 -2
  124. package/build/__tests__/unit/networkInterceptor.test.js.map +1 -1
  125. package/build/__tests__/unit/overlayGuard.test.d.ts +2 -0
  126. package/build/__tests__/unit/overlayGuard.test.d.ts.map +1 -0
  127. package/build/__tests__/unit/overlayGuard.test.js +123 -0
  128. package/build/__tests__/unit/overlayGuard.test.js.map +1 -0
  129. package/build/__tests__/unit/pendingExecutions.test.js +0 -6
  130. package/build/__tests__/unit/pendingExecutions.test.js.map +1 -1
  131. package/build/__tests__/unit/probe.test.js +0 -1
  132. package/build/__tests__/unit/probe.test.js.map +1 -1
  133. package/build/__tests__/unit/projectMemory.test.js +6 -7
  134. package/build/__tests__/unit/projectMemory.test.js.map +1 -1
  135. package/build/__tests__/unit/projectStore.test.js +2 -2
  136. package/build/__tests__/unit/projectStore.test.js.map +1 -1
  137. package/build/__tests__/unit/reloadAppAutoConnect.test.js +0 -9
  138. package/build/__tests__/unit/reloadAppAutoConnect.test.js.map +1 -1
  139. package/build/__tests__/unit/rnGlobalsBootstrap.test.js +3 -3
  140. package/build/__tests__/unit/rnGlobalsBootstrap.test.js.map +1 -1
  141. package/build/__tests__/unit/routeHistory.test.d.ts +2 -0
  142. package/build/__tests__/unit/routeHistory.test.d.ts.map +1 -0
  143. package/build/__tests__/unit/routeHistory.test.js +136 -0
  144. package/build/__tests__/unit/routeHistory.test.js.map +1 -0
  145. package/build/__tests__/unit/screenSpace.test.js +30 -13
  146. package/build/__tests__/unit/screenSpace.test.js.map +1 -1
  147. package/build/__tests__/unit/screenState.test.js +3 -6
  148. package/build/__tests__/unit/screenState.test.js.map +1 -1
  149. package/build/__tests__/unit/screenStateInputValue.test.js +0 -7
  150. package/build/__tests__/unit/screenStateInputValue.test.js.map +1 -1
  151. package/build/__tests__/unit/screenStateKeyboard.test.js +0 -1
  152. package/build/__tests__/unit/screenStateKeyboard.test.js.map +1 -1
  153. package/build/__tests__/unit/screenshot-diff.test.js +0 -6
  154. package/build/__tests__/unit/screenshot-diff.test.js.map +1 -1
  155. package/build/__tests__/unit/sdkMirrorPoller.test.js +0 -8
  156. package/build/__tests__/unit/sdkMirrorPoller.test.js.map +1 -1
  157. package/build/__tests__/unit/settleAndDiff.test.js +0 -11
  158. package/build/__tests__/unit/settleAndDiff.test.js.map +1 -1
  159. package/build/__tests__/unit/signedVerdict.test.js +0 -5
  160. package/build/__tests__/unit/signedVerdict.test.js.map +1 -1
  161. package/build/__tests__/unit/swipeDirection.test.js +6 -9
  162. package/build/__tests__/unit/swipeDirection.test.js.map +1 -1
  163. package/build/__tests__/unit/symbolicate.test.js +0 -1
  164. package/build/__tests__/unit/symbolicate.test.js.map +1 -1
  165. package/build/__tests__/unit/tap.test.js +2 -40
  166. package/build/__tests__/unit/tap.test.js.map +1 -1
  167. package/build/__tests__/unit/textEntry.test.js +0 -27
  168. package/build/__tests__/unit/textEntry.test.js.map +1 -1
  169. package/build/__tests__/unit/toolDescriptions.test.js +0 -40
  170. package/build/__tests__/unit/toolDescriptions.test.js.map +1 -1
  171. package/build/__tests__/unit/toolsJson.test.js +0 -10
  172. package/build/__tests__/unit/toolsJson.test.js.map +1 -1
  173. package/build/__tests__/unit/transportClassify.test.js +0 -20
  174. package/build/__tests__/unit/transportClassify.test.js.map +1 -1
  175. package/build/__tests__/unit/truncate.test.js +0 -5
  176. package/build/__tests__/unit/truncate.test.js.map +1 -1
  177. package/build/__tests__/unit/usageCache.test.js +0 -3
  178. package/build/__tests__/unit/usageCache.test.js.map +1 -1
  179. package/build/__tests__/unit/usageGate.test.js +1 -1
  180. package/build/__tests__/unit/usageGate.test.js.map +1 -1
  181. package/build/__tests__/unit/visibility.test.js +0 -8
  182. package/build/__tests__/unit/visibility.test.js.map +1 -1
  183. package/build/__tests__/unit/waitForMeasurements.test.js +2 -7
  184. package/build/__tests__/unit/waitForMeasurements.test.js.map +1 -1
  185. package/build/bin.js +0 -18
  186. package/build/bin.js.map +1 -1
  187. package/build/core/android.d.ts +0 -120
  188. package/build/core/android.d.ts.map +1 -1
  189. package/build/core/android.js +4 -259
  190. package/build/core/android.js.map +1 -1
  191. package/build/core/appContext.d.ts +0 -50
  192. package/build/core/appContext.d.ts.map +1 -1
  193. package/build/core/appContext.js +0 -50
  194. package/build/core/appContext.js.map +1 -1
  195. package/build/core/appDetection.d.ts +0 -16
  196. package/build/core/appDetection.d.ts.map +1 -1
  197. package/build/core/appDetection.js +0 -36
  198. package/build/core/appDetection.js.map +1 -1
  199. package/build/core/appRequest.d.ts +0 -12
  200. package/build/core/appRequest.d.ts.map +1 -1
  201. package/build/core/appRequest.js +0 -15
  202. package/build/core/appRequest.js.map +1 -1
  203. package/build/core/bufferConfig.d.ts +0 -2
  204. package/build/core/bufferConfig.d.ts.map +1 -1
  205. package/build/core/bufferConfig.js +0 -2
  206. package/build/core/bufferConfig.js.map +1 -1
  207. package/build/core/bundle.js +0 -40
  208. package/build/core/bundle.js.map +1 -1
  209. package/build/core/componentSearch.d.ts +0 -19
  210. package/build/core/componentSearch.d.ts.map +1 -1
  211. package/build/core/componentSearch.js +1 -22
  212. package/build/core/componentSearch.js.map +1 -1
  213. package/build/core/componentSource.d.ts +0 -21
  214. package/build/core/componentSource.d.ts.map +1 -1
  215. package/build/core/componentSource.js +0 -27
  216. package/build/core/componentSource.js.map +1 -1
  217. package/build/core/componentTree.js +0 -12
  218. package/build/core/componentTree.js.map +1 -1
  219. package/build/core/config.d.ts +0 -8
  220. package/build/core/config.d.ts.map +1 -1
  221. package/build/core/config.js +0 -10
  222. package/build/core/config.js.map +1 -1
  223. package/build/core/connection.d.ts +0 -76
  224. package/build/core/connection.d.ts.map +1 -1
  225. package/build/core/connection.js +15 -340
  226. package/build/core/connection.js.map +1 -1
  227. package/build/core/connectionState.d.ts +0 -78
  228. package/build/core/connectionState.d.ts.map +1 -1
  229. package/build/core/connectionState.js +4 -92
  230. package/build/core/connectionState.js.map +1 -1
  231. package/build/core/debugGlobals.js +0 -68
  232. package/build/core/debugGlobals.js.map +1 -1
  233. package/build/core/deviceDiscovery.d.ts +0 -5
  234. package/build/core/deviceDiscovery.d.ts.map +1 -1
  235. package/build/core/deviceDiscovery.js +0 -12
  236. package/build/core/deviceDiscovery.js.map +1 -1
  237. package/build/core/deviceResolver.d.ts +0 -10
  238. package/build/core/deviceResolver.d.ts.map +1 -1
  239. package/build/core/deviceResolver.js +0 -54
  240. package/build/core/deviceResolver.js.map +1 -1
  241. package/build/core/epochRender.d.ts +0 -12
  242. package/build/core/epochRender.d.ts.map +1 -1
  243. package/build/core/epochRender.js +0 -12
  244. package/build/core/epochRender.js.map +1 -1
  245. package/build/core/errorScreenParser.d.ts +0 -21
  246. package/build/core/errorScreenParser.d.ts.map +1 -1
  247. package/build/core/errorScreenParser.js +0 -50
  248. package/build/core/errorScreenParser.js.map +1 -1
  249. package/build/core/errors.d.ts +0 -15
  250. package/build/core/errors.d.ts.map +1 -1
  251. package/build/core/errors.js +0 -15
  252. package/build/core/errors.js.map +1 -1
  253. package/build/core/escapeNonAscii.d.ts +0 -9
  254. package/build/core/escapeNonAscii.d.ts.map +1 -1
  255. package/build/core/escapeNonAscii.js +1 -43
  256. package/build/core/escapeNonAscii.js.map +1 -1
  257. package/build/core/exec.d.ts +0 -12
  258. package/build/core/exec.d.ts.map +1 -1
  259. package/build/core/exec.js +2 -14
  260. package/build/core/exec.js.map +1 -1
  261. package/build/core/executor.js +0 -3
  262. package/build/core/executor.js.map +1 -1
  263. package/build/core/fastRefreshRecorder.js +0 -4
  264. package/build/core/fastRefreshRecorder.js.map +1 -1
  265. package/build/core/fastRefreshTools.js +0 -3
  266. package/build/core/fastRefreshTools.js.map +1 -1
  267. package/build/core/feedback.js +0 -4
  268. package/build/core/feedback.js.map +1 -1
  269. package/build/core/graphqlOperation.d.ts +13 -0
  270. package/build/core/graphqlOperation.d.ts.map +1 -0
  271. package/build/core/graphqlOperation.js +80 -0
  272. package/build/core/graphqlOperation.js.map +1 -0
  273. package/build/core/guides.d.ts +0 -10
  274. package/build/core/guides.d.ts.map +1 -1
  275. package/build/core/guides.js +0 -10
  276. package/build/core/guides.js.map +1 -1
  277. package/build/core/iconSemantics.d.ts +0 -15
  278. package/build/core/iconSemantics.d.ts.map +1 -1
  279. package/build/core/iconSemantics.js +0 -31
  280. package/build/core/iconSemantics.js.map +1 -1
  281. package/build/core/index.js +4 -47
  282. package/build/core/index.js.map +1 -1
  283. package/build/core/injected/visibility.d.ts +0 -30
  284. package/build/core/injected/visibility.d.ts.map +1 -1
  285. package/build/core/injected/visibility.js +0 -33
  286. package/build/core/injected/visibility.js.map +1 -1
  287. package/build/core/injectedFilters.d.ts +0 -28
  288. package/build/core/injectedFilters.d.ts.map +1 -1
  289. package/build/core/injectedFilters.js +0 -28
  290. package/build/core/injectedFilters.js.map +1 -1
  291. package/build/core/inputTarget.d.ts +1 -34
  292. package/build/core/inputTarget.d.ts.map +1 -1
  293. package/build/core/inputTarget.js +0 -26
  294. package/build/core/inputTarget.js.map +1 -1
  295. package/build/core/inputTargetTools.d.ts +0 -7
  296. package/build/core/inputTargetTools.d.ts.map +1 -1
  297. package/build/core/inputTargetTools.js +0 -7
  298. package/build/core/inputTargetTools.js.map +1 -1
  299. package/build/core/inspector.d.ts +0 -33
  300. package/build/core/inspector.d.ts.map +1 -1
  301. package/build/core/inspector.js +27 -40
  302. package/build/core/inspector.js.map +1 -1
  303. package/build/core/ios.d.ts +11 -122
  304. package/build/core/ios.d.ts.map +1 -1
  305. package/build/core/ios.js +32 -299
  306. package/build/core/ios.js.map +1 -1
  307. package/build/core/jsExecute.d.ts +0 -100
  308. package/build/core/jsExecute.d.ts.map +1 -1
  309. package/build/core/jsExecute.js +0 -261
  310. package/build/core/jsExecute.js.map +1 -1
  311. package/build/core/jsLogEvents.d.ts +0 -8
  312. package/build/core/jsLogEvents.d.ts.map +1 -1
  313. package/build/core/jsLogEvents.js +0 -8
  314. package/build/core/jsLogEvents.js.map +1 -1
  315. package/build/core/keyboardMetrics.js +0 -8
  316. package/build/core/keyboardMetrics.js.map +1 -1
  317. package/build/core/keyboardRaise.d.ts +0 -8
  318. package/build/core/keyboardRaise.d.ts.map +1 -1
  319. package/build/core/keyboardRaise.js +0 -23
  320. package/build/core/keyboardRaise.js.map +1 -1
  321. package/build/core/license.d.ts +0 -4
  322. package/build/core/license.d.ts.map +1 -1
  323. package/build/core/license.js +2 -55
  324. package/build/core/license.js.map +1 -1
  325. package/build/core/logDiagnosis.d.ts +0 -34
  326. package/build/core/logDiagnosis.d.ts.map +1 -1
  327. package/build/core/logDiagnosis.js +0 -15
  328. package/build/core/logDiagnosis.js.map +1 -1
  329. package/build/core/logEventFormat.d.ts +0 -1
  330. package/build/core/logEventFormat.d.ts.map +1 -1
  331. package/build/core/logEventFormat.js +0 -30
  332. package/build/core/logEventFormat.js.map +1 -1
  333. package/build/core/logEvents.d.ts +0 -54
  334. package/build/core/logEvents.d.ts.map +1 -1
  335. package/build/core/logEvents.js +0 -37
  336. package/build/core/logEvents.js.map +1 -1
  337. package/build/core/logGrouping.d.ts +0 -9
  338. package/build/core/logGrouping.d.ts.map +1 -1
  339. package/build/core/logGrouping.js +0 -17
  340. package/build/core/logGrouping.js.map +1 -1
  341. package/build/core/logOwnership.d.ts +0 -17
  342. package/build/core/logOwnership.d.ts.map +1 -1
  343. package/build/core/logOwnership.js +0 -34
  344. package/build/core/logOwnership.js.map +1 -1
  345. package/build/core/logRelevance.d.ts +0 -8
  346. package/build/core/logRelevance.d.ts.map +1 -1
  347. package/build/core/logRelevance.js +0 -15
  348. package/build/core/logRelevance.js.map +1 -1
  349. package/build/core/logSourceAndroid.d.ts +0 -8
  350. package/build/core/logSourceAndroid.d.ts.map +1 -1
  351. package/build/core/logSourceAndroid.js +0 -11
  352. package/build/core/logSourceAndroid.js.map +1 -1
  353. package/build/core/logSourceIos.d.ts +0 -8
  354. package/build/core/logSourceIos.d.ts.map +1 -1
  355. package/build/core/logSourceIos.js +1 -24
  356. package/build/core/logSourceIos.js.map +1 -1
  357. package/build/core/logbox.d.ts +0 -30
  358. package/build/core/logbox.d.ts.map +1 -1
  359. package/build/core/logbox.js +0 -43
  360. package/build/core/logbox.js.map +1 -1
  361. package/build/core/logs.d.ts +0 -6
  362. package/build/core/logs.d.ts.map +1 -1
  363. package/build/core/logs.js +0 -19
  364. package/build/core/logs.js.map +1 -1
  365. package/build/core/metro.d.ts +0 -14
  366. package/build/core/metro.d.ts.map +1 -1
  367. package/build/core/metro.js +1 -32
  368. package/build/core/metro.js.map +1 -1
  369. package/build/core/moduleRegistry.d.ts +0 -17
  370. package/build/core/moduleRegistry.d.ts.map +1 -1
  371. package/build/core/moduleRegistry.js +0 -17
  372. package/build/core/moduleRegistry.js.map +1 -1
  373. package/build/core/nativeInputValue.d.ts +0 -29
  374. package/build/core/nativeInputValue.d.ts.map +1 -1
  375. package/build/core/nativeInputValue.js +0 -18
  376. package/build/core/nativeInputValue.js.map +1 -1
  377. package/build/core/nativeLogs.d.ts +0 -47
  378. package/build/core/nativeLogs.d.ts.map +1 -1
  379. package/build/core/nativeLogs.js +0 -82
  380. package/build/core/nativeLogs.js.map +1 -1
  381. package/build/core/nativeOnlyHints.js +0 -4
  382. package/build/core/nativeOnlyHints.js.map +1 -1
  383. package/build/core/navigate.d.ts +0 -15
  384. package/build/core/navigate.d.ts.map +1 -1
  385. package/build/core/navigate.js +0 -22
  386. package/build/core/navigate.js.map +1 -1
  387. package/build/core/navigation.d.ts +0 -60
  388. package/build/core/navigation.d.ts.map +1 -1
  389. package/build/core/navigation.js +0 -60
  390. package/build/core/navigation.js.map +1 -1
  391. package/build/core/network.d.ts +0 -5
  392. package/build/core/network.d.ts.map +1 -1
  393. package/build/core/network.js +10 -31
  394. package/build/core/network.js.map +1 -1
  395. package/build/core/networkInterceptor.d.ts +0 -19
  396. package/build/core/networkInterceptor.d.ts.map +1 -1
  397. package/build/core/networkInterceptor.js +3 -27
  398. package/build/core/networkInterceptor.js.map +1 -1
  399. package/build/core/ocr.d.ts +0 -31
  400. package/build/core/ocr.d.ts.map +1 -1
  401. package/build/core/ocr.js +1 -71
  402. package/build/core/ocr.js.map +1 -1
  403. package/build/core/paths.js +0 -1
  404. package/build/core/paths.js.map +1 -1
  405. package/build/core/posthog.js +0 -7
  406. package/build/core/posthog.js.map +1 -1
  407. package/build/core/pressables.d.ts +0 -25
  408. package/build/core/pressables.d.ts.map +1 -1
  409. package/build/core/pressables.js +0 -37
  410. package/build/core/pressables.js.map +1 -1
  411. package/build/core/probe.d.ts +0 -9
  412. package/build/core/probe.d.ts.map +1 -1
  413. package/build/core/probe.js +0 -9
  414. package/build/core/probe.js.map +1 -1
  415. package/build/core/projectMemory.d.ts +0 -2
  416. package/build/core/projectMemory.d.ts.map +1 -1
  417. package/build/core/projectMemory.js +2 -7
  418. package/build/core/projectMemory.js.map +1 -1
  419. package/build/core/projectStore.d.ts +0 -18
  420. package/build/core/projectStore.d.ts.map +1 -1
  421. package/build/core/projectStore.js +0 -19
  422. package/build/core/projectStore.js.map +1 -1
  423. package/build/core/promiseHandles.d.ts +0 -13
  424. package/build/core/promiseHandles.d.ts.map +1 -1
  425. package/build/core/promiseHandles.js +0 -25
  426. package/build/core/promiseHandles.js.map +1 -1
  427. package/build/core/register.js +0 -44
  428. package/build/core/register.js.map +1 -1
  429. package/build/core/rnGlobalsBootstrap.d.ts +0 -13
  430. package/build/core/rnGlobalsBootstrap.d.ts.map +1 -1
  431. package/build/core/rnGlobalsBootstrap.js +0 -22
  432. package/build/core/rnGlobalsBootstrap.js.map +1 -1
  433. package/build/core/routeHistory.d.ts +24 -0
  434. package/build/core/routeHistory.d.ts.map +1 -0
  435. package/build/core/routeHistory.js +186 -0
  436. package/build/core/routeHistory.js.map +1 -0
  437. package/build/core/screenLayout.d.ts +0 -11
  438. package/build/core/screenLayout.d.ts.map +1 -1
  439. package/build/core/screenLayout.js +24 -61
  440. package/build/core/screenLayout.js.map +1 -1
  441. package/build/core/screenSpace.d.ts +32 -44
  442. package/build/core/screenSpace.d.ts.map +1 -1
  443. package/build/core/screenSpace.js +80 -46
  444. package/build/core/screenSpace.js.map +1 -1
  445. package/build/core/screenSpaceDevice.d.ts +0 -8
  446. package/build/core/screenSpaceDevice.d.ts.map +1 -1
  447. package/build/core/screenSpaceDevice.js +31 -14
  448. package/build/core/screenSpaceDevice.js.map +1 -1
  449. package/build/core/screenState.d.ts +2 -74
  450. package/build/core/screenState.d.ts.map +1 -1
  451. package/build/core/screenState.js +204 -145
  452. package/build/core/screenState.js.map +1 -1
  453. package/build/core/sdkBridge.js +0 -8
  454. package/build/core/sdkBridge.js.map +1 -1
  455. package/build/core/sdkMirrorPoller.d.ts +0 -18
  456. package/build/core/sdkMirrorPoller.d.ts.map +1 -1
  457. package/build/core/sdkMirrorPoller.js +0 -47
  458. package/build/core/sdkMirrorPoller.js.map +1 -1
  459. package/build/core/selectionBuffer.d.ts +0 -6
  460. package/build/core/selectionBuffer.d.ts.map +1 -1
  461. package/build/core/selectionBuffer.js +0 -5
  462. package/build/core/selectionBuffer.js.map +1 -1
  463. package/build/core/signedVerdict.js +0 -6
  464. package/build/core/signedVerdict.js.map +1 -1
  465. package/build/core/state.d.ts +0 -31
  466. package/build/core/state.d.ts.map +1 -1
  467. package/build/core/state.js +0 -57
  468. package/build/core/state.js.map +1 -1
  469. package/build/core/symbolicate.js +0 -11
  470. package/build/core/symbolicate.js.map +1 -1
  471. package/build/core/telemetry.d.ts +0 -47
  472. package/build/core/telemetry.d.ts.map +1 -1
  473. package/build/core/telemetry.js +1 -173
  474. package/build/core/telemetry.js.map +1 -1
  475. package/build/core/textEntry.d.ts +0 -34
  476. package/build/core/textEntry.d.ts.map +1 -1
  477. package/build/core/textEntry.js +0 -73
  478. package/build/core/textEntry.js.map +1 -1
  479. package/build/core/toolHelpers.d.ts +0 -12
  480. package/build/core/toolHelpers.d.ts.map +1 -1
  481. package/build/core/toolHelpers.js +2 -47
  482. package/build/core/toolHelpers.js.map +1 -1
  483. package/build/core/truncate.d.ts +0 -17
  484. package/build/core/truncate.d.ts.map +1 -1
  485. package/build/core/truncate.js +0 -32
  486. package/build/core/truncate.js.map +1 -1
  487. package/build/core/types.d.ts +0 -6
  488. package/build/core/types.d.ts.map +1 -1
  489. package/build/core/usageCache.js +0 -12
  490. package/build/core/usageCache.js.map +1 -1
  491. package/build/index.js +0 -18
  492. package/build/index.js.map +1 -1
  493. package/build/pro/coordinateMiss.d.ts +0 -30
  494. package/build/pro/coordinateMiss.d.ts.map +1 -1
  495. package/build/pro/coordinateMiss.js +0 -17
  496. package/build/pro/coordinateMiss.js.map +1 -1
  497. package/build/pro/overlayGuard.d.ts +27 -0
  498. package/build/pro/overlayGuard.d.ts.map +1 -0
  499. package/build/pro/overlayGuard.js +103 -0
  500. package/build/pro/overlayGuard.js.map +1 -0
  501. package/build/pro/screenshot-diff.js +4 -6
  502. package/build/pro/screenshot-diff.js.map +1 -1
  503. package/build/pro/tap.d.ts +0 -55
  504. package/build/pro/tap.d.ts.map +1 -1
  505. package/build/pro/tap.js +72 -316
  506. package/build/pro/tap.js.map +1 -1
  507. package/build/pro/usageGate.js +0 -9
  508. package/build/pro/usageGate.js.map +1 -1
  509. package/build/pro/usageNotifications.js +1 -13
  510. package/build/pro/usageNotifications.js.map +1 -1
  511. package/build/pro/verifyAction.d.ts +0 -13
  512. package/build/pro/verifyAction.d.ts.map +1 -1
  513. package/build/pro/verifyAction.js +0 -45
  514. package/build/pro/verifyAction.js.map +1 -1
  515. package/build/tools/_deviceArg.d.ts +0 -35
  516. package/build/tools/_deviceArg.d.ts.map +1 -1
  517. package/build/tools/_deviceArg.js +0 -35
  518. package/build/tools/_deviceArg.js.map +1 -1
  519. package/build/tools/accountTools.js +2 -2
  520. package/build/tools/accountTools.js.map +1 -1
  521. package/build/tools/bundleTools.js +0 -16
  522. package/build/tools/bundleTools.js.map +1 -1
  523. package/build/tools/componentTools.d.ts.map +1 -1
  524. package/build/tools/componentTools.js +47 -53
  525. package/build/tools/componentTools.js.map +1 -1
  526. package/build/tools/connectionTools.js +1 -59
  527. package/build/tools/connectionTools.js.map +1 -1
  528. package/build/tools/deviceTools.js +0 -11
  529. package/build/tools/deviceTools.js.map +1 -1
  530. package/build/tools/executionTools.js +1 -18
  531. package/build/tools/executionTools.js.map +1 -1
  532. package/build/tools/interactionTools.d.ts +0 -10
  533. package/build/tools/interactionTools.d.ts.map +1 -1
  534. package/build/tools/interactionTools.js +0 -57
  535. package/build/tools/interactionTools.js.map +1 -1
  536. package/build/tools/logTools.d.ts +0 -13
  537. package/build/tools/logTools.d.ts.map +1 -1
  538. package/build/tools/logTools.js +2 -126
  539. package/build/tools/logTools.js.map +1 -1
  540. package/build/tools/metaTools.js +0 -10
  541. package/build/tools/metaTools.js.map +1 -1
  542. package/build/tools/networkTools.js +4 -43
  543. package/build/tools/networkTools.js.map +1 -1
  544. package/build/tools/requestTools.js +0 -5
  545. package/build/tools/requestTools.js.map +1 -1
  546. package/build/tools/screenshotTools.js +5 -111
  547. package/build/tools/screenshotTools.js.map +1 -1
  548. package/package.json +2 -2
  549. package/tools.json +1 -0
@@ -11,26 +11,13 @@ import { scheduleAppDetection } from "./appDetection.js";
11
11
  import { markConnectionEstablished } from "./jsExecute.js";
12
12
  import { startSdkMirrorPoller, stopSdkMirrorPoller } from "./sdkMirrorPoller.js";
13
13
  import { DEFAULT_RECONNECTION_CONFIG, MIN_STABLE_CONNECTION_MS, initConnectionState, updateConnectionState, getConnectionState, recordConnectionGap, closeConnectionGap, saveConnectionMetadata, clearConnectionMetadata, getConnectionMetadata, saveReconnectionTimer, cancelReconnectionTimer, calculateBackoffDelay, initContextHealth, markContextHealthy, markContextStale, getContextHealth, updateContextHealth, formatDuration, recordConnectionEvent, } from "./connectionState.js";
14
- // Connection locks to prevent concurrent connection attempts to the same device
15
14
  const connectionLocks = new Set();
16
- // Last CDP target id seen per device name, used to detect app relaunches.
17
15
  const lastTargetIdByDevice = new Map();
18
- // Track Network.enable message IDs to detect CDP network support
19
16
  const pendingNetworkEnableIds = new Set();
20
- // Track SDK-probe message IDs so we can decode their responses and set
21
- // app.sdkPresent. The probe runs on connect and then periodically because
22
- // the SDK's init() may execute after we attach.
23
- const pendingSdkProbeIds = new Map(); // msgId -> appKey
17
+ const pendingSdkProbeIds = new Map();
24
18
  const SDK_PROBE_INITIAL_DELAY_MS = 200;
25
19
  const SDK_PROBE_INTERVAL_MS = 3000;
26
- // Consecutive SDK-absent probes required to flip sdkPresent true→false.
27
- // Hysteresis against the post-reload window where the JS context exists but
28
- // the SDK's init() hasn't re-run yet (see ConnectedApp.sdkMissCount).
29
20
  const SDK_ABSENCE_CONFIRM_COUNT = 2;
30
- // Fast re-probe schedule after a context is (re)created, so the SDK is
31
- // re-detected within a few hundred ms instead of waiting up to a full
32
- // SDK_PROBE_INTERVAL_MS tick. Covers reloads not driven by reload_app
33
- // (shake-to-reload, Metro "r").
34
21
  const SDK_REPROBE_DELAYS_MS = [200, 500, 1000, 2000];
35
22
  function sendSdkProbe(ws, appKey) {
36
23
  if (ws.readyState !== WebSocket.OPEN)
@@ -65,16 +52,11 @@ function startSdkProbeLoop(ws, appKey) {
65
52
  if (app) {
66
53
  app.sdkProbeTimer = interval;
67
54
  }
68
- // Best-effort cleanup if the socket closes before the initial probe fires
69
55
  ws.once("close", () => {
70
56
  clearTimeout(initial);
71
57
  clearInterval(interval);
72
58
  });
73
59
  }
74
- // Fire a burst of SDK probes on the heels of a (re)created execution context.
75
- // After a reload the context is recreated but the SDK's init() re-runs slightly
76
- // later; without this, sdkPresent only recovers on the next 3s interval tick,
77
- // widening the window where get_network_requests falls off the SDK buffer.
78
60
  function scheduleFastSdkReprobe(ws, appKey) {
79
61
  for (const delay of SDK_REPROBE_DELAYS_MS) {
80
62
  const t = setTimeout(() => {
@@ -85,48 +67,24 @@ function scheduleFastSdkReprobe(ws, appKey) {
85
67
  ws.once("close", () => clearTimeout(t));
86
68
  }
87
69
  }
88
- // Suppress auto-reconnection for intentionally disconnected devices
89
70
  const reconnectionSuppressed = new Set();
90
- /**
91
- * Suppress auto-reconnection for all current connections.
92
- * Used by disconnect_metro to prevent close handlers from re-connecting.
93
- */
94
71
  export function suppressReconnection() {
95
72
  for (const key of connectedApps.keys()) {
96
73
  reconnectionSuppressed.add(key);
97
74
  }
98
75
  }
99
- /**
100
- * Suppress auto-reconnection for a specific connection key.
101
- * Used by disconnect_metro with device targeting.
102
- */
103
76
  export function suppressReconnectionForKey(appKey) {
104
77
  reconnectionSuppressed.add(appKey);
105
78
  }
106
- /**
107
- * Clear reconnection suppression (called when user explicitly reconnects via scan_metro).
108
- */
109
79
  export function clearReconnectionSuppression() {
110
80
  reconnectionSuppressed.clear();
111
81
  }
112
82
  const STALE_ACTIVITY_THRESHOLD_MS = 30_000;
113
- const PING_INTERVAL_MS = 1_000; // WebSocket ping/pong keepalive interval
114
- /**
115
- * Max time (ms) to wait for a Runtime.evaluate("1+1") reply during liveness probing.
116
- * Tight budget: live targets respond in under 50ms; zombie CDP targets never respond.
117
- */
83
+ const PING_INTERVAL_MS = 1_000;
118
84
  const PROBE_TIMEOUT_MS = 1500;
119
85
  const RECONNECT_SETTLE_MS = 500;
120
- /**
121
- * Creates a WebSocket connection with Origin header fallback.
122
- * RN 0.85+ Metro requires the Origin header, but Expo SDK 55 Bridgeless
123
- * rejects connections that include one. The rejection can manifest as:
124
- * - Connection refused before open (error/close before open event)
125
- * - Immediate close after open (open fires, then close with code 1006)
126
- * Tries with Origin first, falls back to without on quick failure.
127
- */
128
86
  export function createWebSocketWithOriginFallback(url, timeoutMs = 5000) {
129
- const STABILIZE_MS = 500; // Wait after open to detect immediate close
87
+ const STABILIZE_MS = 500;
130
88
  return new Promise((resolve, reject) => {
131
89
  const wsUrl = new URL(url);
132
90
  const origin = `http://${wsUrl.hostname}:${wsUrl.port}`;
@@ -138,7 +96,6 @@ export function createWebSocketWithOriginFallback(url, timeoutMs = 5000) {
138
96
  ws.on("open", () => {
139
97
  opened = true;
140
98
  if (withOrigin) {
141
- // Wait briefly to detect immediate close (Expo SDK 55 pattern)
142
99
  setTimeout(() => {
143
100
  if (settled)
144
101
  return;
@@ -147,7 +104,6 @@ export function createWebSocketWithOriginFallback(url, timeoutMs = 5000) {
147
104
  console.error("[execbro] WebSocket connected with Origin header");
148
105
  resolve(ws);
149
106
  }
150
- // If not OPEN, the close handler will trigger fallback
151
107
  }, STABILIZE_MS);
152
108
  }
153
109
  else {
@@ -185,7 +141,6 @@ export function createWebSocketWithOriginFallback(url, timeoutMs = 5000) {
185
141
  }, timeoutMs);
186
142
  });
187
143
  }
188
- // Helper to find appKey from device info by searching connectedApps
189
144
  function findAppKeyForDevice(device) {
190
145
  for (const [key, app] of connectedApps.entries()) {
191
146
  if (app.deviceInfo.id === device.id) {
@@ -194,7 +149,6 @@ function findAppKeyForDevice(device) {
194
149
  }
195
150
  return null;
196
151
  }
197
- // Helper to convert WebSocket readyState to readable name
198
152
  export function getWebSocketStateName(state) {
199
153
  switch (state) {
200
154
  case WebSocket.CONNECTING: return "CONNECTING";
@@ -204,24 +158,13 @@ export function getWebSocketStateName(state) {
204
158
  default: return `UNKNOWN(${state})`;
205
159
  }
206
160
  }
207
- /**
208
- * Purge stale connections for scanned ports.
209
- * When Metro restarts with a different app, /json returns new device IDs.
210
- * Old connections keyed by the previous device IDs remain in connectedApps.
211
- * This function removes connections on scanned ports whose device IDs
212
- * are no longer present in the fresh device list from Metro.
213
- */
214
161
  export function purgeStaleConnectionsForPorts(freshDevicesByPort) {
215
162
  const purged = [];
216
163
  const scannedPorts = new Set(freshDevicesByPort.keys());
217
164
  for (const [appKey, app] of connectedApps.entries()) {
218
- // Only check connections on ports we just scanned
219
165
  if (!scannedPorts.has(app.port))
220
166
  continue;
221
167
  const freshDevices = freshDevicesByPort.get(app.port) || [];
222
- // Check both device ID and appId — device IDs are per-physical-device
223
- // (stable across Metro restarts), so a different app on the same device
224
- // would have the same ID but a different appId
225
168
  const matchingDevice = freshDevices.find(d => d.id === app.deviceInfo.id);
226
169
  const stillValid = matchingDevice && matchingDevice.appId === app.deviceInfo.appId;
227
170
  if (!stillValid) {
@@ -230,7 +173,6 @@ export function purgeStaleConnectionsForPorts(freshDevicesByPort) {
230
173
  ? "device no longer in Metro /json"
231
174
  : `appId changed: ${app.deviceInfo.appId} → ${matchingDevice.appId}`;
232
175
  console.error(`[execbro] Purging stale connection: ${name} (port ${app.port}, id ${app.deviceInfo.id}) — ${reason}`);
233
- // Suppress reconnection so close handler doesn't try to reconnect
234
176
  reconnectionSuppressed.add(appKey);
235
177
  cancelReconnectionTimer(appKey);
236
178
  clearLastCDPMessageTime(appKey);
@@ -238,14 +180,13 @@ export function purgeStaleConnectionsForPorts(freshDevicesByPort) {
238
180
  try {
239
181
  app.ws.close();
240
182
  }
241
- catch { /* ignore */ }
183
+ catch { }
242
184
  connectedApps.delete(appKey);
243
185
  purged.push(name);
244
186
  }
245
187
  }
246
188
  return purged;
247
189
  }
248
- // Format CDP RemoteObject to readable string
249
190
  export function formatRemoteObject(result) {
250
191
  if (result.type === "undefined") {
251
192
  return "undefined";
@@ -253,44 +194,31 @@ export function formatRemoteObject(result) {
253
194
  if (result.subtype === "null") {
254
195
  return "null";
255
196
  }
256
- // For objects/arrays with a value, stringify it
257
197
  if (result.value !== undefined) {
258
198
  if (typeof result.value === "object") {
259
199
  return JSON.stringify(result.value, null, 2);
260
200
  }
261
201
  return String(result.value);
262
202
  }
263
- // Use description for complex objects
264
203
  if (result.description) {
265
204
  return result.description;
266
205
  }
267
- // Handle unserializable values (NaN, Infinity, etc.)
268
206
  if (result.unserializableValue) {
269
207
  return result.unserializableValue;
270
208
  }
271
209
  return `[${result.type}${result.subtype ? ` ${result.subtype}` : ""}]`;
272
210
  }
273
- /**
274
- * Extract a clean, informative error message from CDP exception details
275
- * Handles various error formats from Hermes and other JS engines
276
- */
277
211
  function extractExceptionMessage(exceptionDetails) {
278
212
  const parts = [];
279
- // Get the exception object if available
280
213
  const exc = exceptionDetails.exception;
281
214
  if (exc) {
282
- // For error objects, className tells us the error type (ReferenceError, TypeError, etc.)
283
215
  const errorType = exc.className || (exc.subtype === 'error' ? 'Error' : '');
284
- // The description usually contains "ErrorType: message" or full stack trace
285
- // We want to extract just the first line (the actual error message)
286
216
  if (exc.description) {
287
217
  const firstLine = exc.description.split('\n')[0].trim();
288
- // If description already includes the error type, use it directly
289
218
  if (firstLine.includes(':')) {
290
219
  parts.push(firstLine);
291
220
  }
292
221
  else if (errorType) {
293
- // Combine error type with description
294
222
  parts.push(`${errorType}: ${firstLine}`);
295
223
  }
296
224
  else {
@@ -298,7 +226,6 @@ function extractExceptionMessage(exceptionDetails) {
298
226
  }
299
227
  }
300
228
  else if (exc.value !== undefined) {
301
- // For primitive exceptions (throw "string" or throw 123)
302
229
  const valueStr = typeof exc.value === 'string' ? exc.value : JSON.stringify(exc.value);
303
230
  if (errorType) {
304
231
  parts.push(`${errorType}: ${valueStr}`);
@@ -308,32 +235,25 @@ function extractExceptionMessage(exceptionDetails) {
308
235
  }
309
236
  }
310
237
  else if (errorType) {
311
- // Just the error type, no message
312
238
  parts.push(errorType);
313
239
  }
314
240
  }
315
- // Fall back to exceptionDetails.text if we couldn't extract from exception object
316
- // But avoid just "Uncaught" which is not helpful
317
241
  if (parts.length === 0) {
318
242
  const text = exceptionDetails.text;
319
243
  if (text && text.toLowerCase() !== 'uncaught') {
320
244
  parts.push(text);
321
245
  }
322
246
  }
323
- // Add location info for syntax/compilation errors (helps identify the problem)
324
247
  if (exceptionDetails.lineNumber !== undefined && exceptionDetails.columnNumber !== undefined) {
325
- // Only add location if it's meaningful (not 0:0 which is often just wrapper)
326
248
  if (exceptionDetails.lineNumber > 0 || exceptionDetails.columnNumber > 0) {
327
249
  parts.push(`at line ${exceptionDetails.lineNumber}:${exceptionDetails.columnNumber}`);
328
250
  }
329
251
  }
330
- // If we still have nothing, provide a generic message
331
252
  if (parts.length === 0) {
332
253
  return 'JavaScript execution failed (no error details available)';
333
254
  }
334
255
  return parts.join(' ');
335
256
  }
336
- // Format a CDP object preview recursively
337
257
  function formatPreview(preview) {
338
258
  const isArray = preview.subtype === "array";
339
259
  const props = preview.properties || [];
@@ -358,40 +278,32 @@ function formatPreview(preview) {
358
278
  ? `[${formatted.join(", ")}${overflow}]`
359
279
  : `{${formatted.join(", ")}${overflow}}`;
360
280
  }
361
- // Format a single CDP console argument (sync — without object resolution)
362
281
  function formatConsoleArg(arg) {
363
- // Explicit undefined (matches browser: console.log(undefined) → "undefined")
364
282
  if (arg.type === "undefined") {
365
283
  return "undefined";
366
284
  }
367
- // Primitives
368
285
  if (arg.type === "string" || arg.type === "number" || arg.type === "boolean") {
369
286
  return String(arg.value);
370
287
  }
371
- // Objects/arrays with preview — expand inline
372
288
  if (arg.preview?.properties) {
373
289
  return formatPreview(arg.preview);
374
290
  }
375
- // Raw value (e.g. null sent as value)
376
291
  if (arg.value !== undefined) {
377
292
  return JSON.stringify(arg.value);
378
293
  }
379
- // Description fallback (functions, symbols, errors without preview)
380
294
  if (arg.description) {
381
295
  return arg.description;
382
296
  }
383
- // Object without any resolved data
384
297
  if (arg.type === "object") {
385
298
  return "[Object]";
386
299
  }
387
300
  return `[${arg.type || "unknown"}]`;
388
301
  }
389
- // Fetch object properties via CDP Runtime.getProperties
390
302
  function fetchObjectProperties(ws, objectId, depth = 2) {
391
303
  return new Promise((resolve) => {
392
304
  const msgId = getNextMessageId();
393
305
  const timeout = setTimeout(() => {
394
- resolve("Object"); // Fallback on timeout
306
+ resolve("Object");
395
307
  }, 3000);
396
308
  const handler = (data) => {
397
309
  try {
@@ -408,7 +320,6 @@ function fetchObjectProperties(ws, objectId, depth = 2) {
408
320
  }
409
321
  }
410
322
  catch {
411
- // Ignore parse errors
412
323
  }
413
324
  };
414
325
  ws.on("message", handler);
@@ -419,11 +330,9 @@ function fetchObjectProperties(ws, objectId, depth = 2) {
419
330
  }));
420
331
  });
421
332
  }
422
- // Format CDP property descriptors into a readable string
423
333
  async function formatCDPProperties(ws, properties, depth) {
424
334
  const parts = [];
425
335
  let isArrayLike = false;
426
- // Detect arrays by checking for numeric keys and "length" property
427
336
  const propNames = properties.filter((p) => !p.isAccessor && p.name !== "__proto__").map((p) => p.name);
428
337
  const hasLength = propNames.includes("length");
429
338
  const numericKeys = propNames.filter((n) => /^\d+$/.test(n));
@@ -447,7 +356,6 @@ async function formatCDPProperties(ws, properties, depth) {
447
356
  ? `[${parts.join(", ")}]`
448
357
  : `{${parts.join(", ")}}`;
449
358
  }
450
- // Format a single property value
451
359
  async function formatPropertyValue(ws, val, remainingDepth) {
452
360
  const type = val.type;
453
361
  const subtype = val.subtype;
@@ -461,11 +369,9 @@ async function formatPropertyValue(ws, val, remainingDepth) {
461
369
  return String(val.value);
462
370
  if (type === "function")
463
371
  return "[Function]";
464
- // Nested object — recurse if depth allows
465
372
  if (type === "object" && val.objectId && remainingDepth > 0) {
466
373
  return fetchObjectProperties(ws, val.objectId, remainingDepth);
467
374
  }
468
- // Object but no depth left — use description
469
375
  if (type === "object") {
470
376
  return val.description || "[Object]";
471
377
  }
@@ -475,26 +381,20 @@ async function formatPropertyValue(ws, val, remainingDepth) {
475
381
  return val.description;
476
382
  return `[${type}]`;
477
383
  }
478
- // Resolve all object args in a console message, returning formatted text
479
384
  async function resolveConsoleArgs(ws, args) {
480
385
  const parts = await Promise.all(args.map(async (arg) => {
481
- // Primitives — format synchronously
482
386
  if (arg.type === "string" || arg.type === "number" || arg.type === "boolean") {
483
387
  return String(arg.value);
484
388
  }
485
- // Objects/arrays with preview — expand inline
486
389
  if (arg.preview?.properties) {
487
390
  return formatPreview(arg.preview);
488
391
  }
489
- // Object with objectId — fetch properties via CDP
490
392
  if (arg.type === "object" && arg.objectId) {
491
393
  return fetchObjectProperties(ws, arg.objectId);
492
394
  }
493
- // Raw value
494
395
  if (arg.value !== undefined) {
495
396
  return JSON.stringify(arg.value);
496
397
  }
497
- // Description fallback
498
398
  if (arg.description) {
499
399
  return arg.description;
500
400
  }
@@ -502,24 +402,19 @@ async function resolveConsoleArgs(ws, args) {
502
402
  }));
503
403
  return parts.join(" ");
504
404
  }
505
- // Handle CDP messages
506
405
  export function handleCDPMessage(message, device, ws) {
507
- // Track last CDP activity for connection liveness detection (per-device)
508
406
  const cdpAppKey = findAppKeyForDevice(device);
509
407
  if (cdpAppKey) {
510
408
  updateLastCDPMessageTime(cdpAppKey, new Date());
511
409
  }
512
410
  const deviceName = device.deviceName || device.title || "unknown";
513
- // Handle responses to our requests (e.g., Runtime.evaluate)
514
411
  if (typeof message.id === "number") {
515
412
  const pending = pendingExecutions.get(message.id);
516
413
  if (pending) {
517
414
  clearTimeout(pending.timeoutId);
518
415
  pendingExecutions.delete(message.id);
519
- // Check for CDP-level error (protocol error, not JS exception)
520
416
  if (message.error) {
521
417
  const error = message.error;
522
- // Build comprehensive error message including code and data if available
523
418
  const parts = [];
524
419
  if (error.message)
525
420
  parts.push(error.message);
@@ -531,21 +426,18 @@ export function handleCDPMessage(message, device, ws) {
531
426
  pending.resolve({ success: false, error: errorMessage });
532
427
  return;
533
428
  }
534
- // Check for JavaScript exception in result
535
429
  const result = message.result;
536
430
  if (result?.exceptionDetails) {
537
431
  const errorMessage = extractExceptionMessage(result.exceptionDetails);
538
432
  pending.resolve({ success: false, error: errorMessage });
539
433
  return;
540
434
  }
541
- // Success - format the result
542
435
  if (result?.result) {
543
436
  pending.resolve({ success: true, result: formatRemoteObject(result.result) });
544
437
  return;
545
438
  }
546
439
  pending.resolve({ success: true, result: "undefined" });
547
440
  }
548
- // Track SDK-probe responses
549
441
  if (pendingSdkProbeIds.has(message.id)) {
550
442
  const appKey = pendingSdkProbeIds.get(message.id);
551
443
  pendingSdkProbeIds.delete(message.id);
@@ -554,15 +446,11 @@ export function handleCDPMessage(message, device, ws) {
554
446
  const result = message.result?.result;
555
447
  const present = result?.value === true;
556
448
  const wasPresent = app.sdkPresent === true;
557
- // Hysteresis on the true→false edge: tolerate transient SDK
558
- // absence right after a reload (context recreated, init() not
559
- // re-run yet) so we don't restore duplicate-prone CDP/interceptor
560
- // writes and bounce get_network_requests off the SDK buffer.
561
449
  let nextPresent = present;
562
450
  if (!present && wasPresent) {
563
451
  app.sdkMissCount = (app.sdkMissCount ?? 0) + 1;
564
452
  if (app.sdkMissCount < SDK_ABSENCE_CONFIRM_COUNT) {
565
- nextPresent = true; // hold "present" until absence is confirmed
453
+ nextPresent = true;
566
454
  }
567
455
  else {
568
456
  app.sdkMissCount = 0;
@@ -574,9 +462,6 @@ export function handleCDPMessage(message, device, ws) {
574
462
  app.sdkPresent = nextPresent;
575
463
  if (nextPresent !== wasPresent) {
576
464
  console.error(`[execbro] SDK ${present ? "detected" : "no longer detected"} on ${app.deviceInfo.title}; CDP/JS-interceptor buffer writes ${present ? "suppressed" : "restored"}`);
577
- // Toggle the in-app interceptor's emit flag so it stops
578
- // (or resumes) producing console.debug lines and CDP
579
- // traffic. Fire-and-forget — the flag is idempotent.
580
465
  if (app.ws.readyState === WebSocket.OPEN) {
581
466
  try {
582
467
  app.ws.send(JSON.stringify({
@@ -589,14 +474,12 @@ export function handleCDPMessage(message, device, ws) {
589
474
  }));
590
475
  }
591
476
  catch {
592
- // ignore — next probe will retry
593
477
  }
594
478
  }
595
479
  }
596
480
  }
597
481
  return;
598
482
  }
599
- // Track Network.enable responses to detect CDP network support
600
483
  if (pendingNetworkEnableIds.has(message.id)) {
601
484
  pendingNetworkEnableIds.delete(message.id);
602
485
  const nAppKey = findAppKeyForDevice(device);
@@ -617,29 +500,22 @@ export function handleCDPMessage(message, device, ws) {
617
500
  return;
618
501
  }
619
502
  const method = message.method;
620
- // Handle Runtime.consoleAPICalled
621
503
  if (method === "Runtime.consoleAPICalled") {
622
504
  const params = message.params;
623
505
  const type = params.type || "log";
624
506
  const level = mapConsoleType(type);
625
507
  const args = params.args || [];
626
- // Check for network interceptor events before processing as logs
627
508
  const interceptorJson = isInterceptorEvent(args);
628
509
  if (interceptorJson !== null) {
629
510
  const iAppKey = findAppKeyForDevice(device);
630
511
  const iApp = iAppKey ? connectedApps.get(iAppKey) : null;
631
- // Skip when CDP Network domain is the source, OR when the in-app
632
- // SDK is the source — both would otherwise produce duplicate
633
- // entries for every request.
634
512
  if (!iApp?.cdpNetworkSupported && !iApp?.sdkPresent) {
635
513
  applyInterceptedEvent(interceptorJson, getNetworkBuffer(deviceName), deviceName);
636
514
  }
637
515
  return;
638
516
  }
639
- // Check if any args need async object resolution
640
517
  const hasObjectArgs = ws && args.some((a) => a.type === "object" && a.objectId && !a.preview?.properties);
641
518
  if (hasObjectArgs) {
642
- // Resolve object args asynchronously via CDP
643
519
  resolveConsoleArgs(ws, args).then((messageText) => {
644
520
  getLogBuffer(deviceName).add({
645
521
  timestamp: new Date(),
@@ -648,7 +524,6 @@ export function handleCDPMessage(message, device, ws) {
648
524
  args: args.map((a) => a.value)
649
525
  });
650
526
  }).catch(() => {
651
- // Fallback to sync formatting on error
652
527
  const messageText = args.map(formatConsoleArg).join(" ");
653
528
  getLogBuffer(deviceName).add({
654
529
  timestamp: new Date(),
@@ -670,7 +545,6 @@ export function handleCDPMessage(message, device, ws) {
670
545
  }
671
546
  }
672
547
  }
673
- // Handle Log.entryAdded
674
548
  if (method === "Log.entryAdded") {
675
549
  const params = message.params;
676
550
  if (params.entry) {
@@ -682,24 +556,12 @@ export function handleCDPMessage(message, device, ws) {
682
556
  });
683
557
  }
684
558
  }
685
- // Handle Network.requestWillBeSent
686
559
  if (method === "Network.requestWillBeSent") {
687
- // Skip CDP capture when the in-app SDK is the source of truth —
688
- // otherwise every request lands in both buffers under different ids.
689
- // The follow-up Network.responseReceived/loadingFinished/loadingFailed
690
- // handlers below only mutate existing entries, so they self-skip.
691
560
  const nAppKey = findAppKeyForDevice(device);
692
561
  const nApp = nAppKey ? connectedApps.get(nAppKey) : null;
693
562
  if (nApp?.sdkPresent) {
694
563
  return;
695
564
  }
696
- // An arriving Network.requestWillBeSent is proof the CDP Network domain
697
- // is active. Some backends (e.g. RN 0.85 Bridgeless/Hermes) enable the
698
- // domain but never send a response to Network.enable, so the
699
- // response-based detection above never fires and cdpNetworkSupported
700
- // stays undefined — leaving the JS interceptor un-suppressed and every
701
- // request double-captured (CDP + interceptor). Mark support here so the
702
- // interceptor dedup guard (see Runtime.consoleAPICalled handler) trips.
703
565
  if (nApp && !nApp.cdpNetworkSupported) {
704
566
  nApp.cdpNetworkSupported = true;
705
567
  console.error(`[execbro] CDP Network domain active (detected via requestWillBeSent on ${nApp.deviceInfo.title})`);
@@ -720,7 +582,6 @@ export function handleCDPMessage(message, device, ws) {
720
582
  };
721
583
  getNetworkBuffer(deviceName).set(params.requestId, request);
722
584
  }
723
- // Handle Network.responseReceived
724
585
  if (method === "Network.responseReceived") {
725
586
  const params = message.params;
726
587
  const existing = getNetworkBuffer(deviceName).get(params.requestId);
@@ -735,7 +596,6 @@ export function handleCDPMessage(message, device, ws) {
735
596
  getNetworkBuffer(deviceName).set(params.requestId, existing);
736
597
  }
737
598
  }
738
- // Handle Network.loadingFinished
739
599
  if (method === "Network.loadingFinished") {
740
600
  const params = message.params;
741
601
  const existing = getNetworkBuffer(deviceName).get(params.requestId);
@@ -748,7 +608,6 @@ export function handleCDPMessage(message, device, ws) {
748
608
  getNetworkBuffer(deviceName).set(params.requestId, existing);
749
609
  }
750
610
  }
751
- // Handle Network.loadingFailed
752
611
  if (method === "Network.loadingFailed") {
753
612
  const params = message.params;
754
613
  const existing = getNetworkBuffer(deviceName).get(params.requestId);
@@ -758,54 +617,39 @@ export function handleCDPMessage(message, device, ws) {
758
617
  getNetworkBuffer(deviceName).set(params.requestId, existing);
759
618
  }
760
619
  }
761
- // Handle Runtime context lifecycle events for health tracking
762
620
  const appKey = findAppKeyForDevice(device);
763
621
  if (appKey) {
764
- // Handle Runtime.executionContextCreated
765
622
  if (method === "Runtime.executionContextCreated") {
766
623
  const params = message.params;
767
624
  markContextHealthy(appKey, params.context.id);
768
625
  console.error(`[execbro] Context created: ${params.context.id}`);
769
- // Re-inject network interceptor and re-check CDP Network support
770
626
  const ctxApp = connectedApps.get(appKey);
771
627
  if (ctxApp?.ws?.readyState === WebSocket.OPEN) {
772
628
  injectNetworkInterceptor(ctxApp.ws);
773
629
  const nEnableId = sendNetworkEnable(ctxApp.ws);
774
630
  pendingNetworkEnableIds.add(nEnableId);
775
- // The context was just recreated (e.g. reload). Re-probe the SDK
776
- // quickly so sdkPresent re-establishes within a few hundred ms
777
- // instead of on the next 3s interval tick.
778
631
  scheduleFastSdkReprobe(ctxApp.ws, appKey);
779
632
  }
780
633
  }
781
- // Handle Runtime.executionContextDestroyed
782
634
  if (method === "Runtime.executionContextDestroyed") {
783
635
  markContextStale(appKey);
784
636
  console.error(`[execbro] Context destroyed`);
785
637
  }
786
- // Handle Runtime.executionContextsCleared
787
638
  if (method === "Runtime.executionContextsCleared") {
788
639
  markContextStale(appKey);
789
- // A replaced JS runtime starts a new app run. Bump so pre-restart
790
- // entries stay addressable and readers can draw the boundary.
791
640
  const clearedDevice = device.deviceName || device.title || "unknown";
792
641
  const nextEpoch = bumpEpoch(clearedDevice);
793
642
  console.error(`[execbro] All contexts cleared — ${clearedDevice} now epoch ${nextEpoch}`);
794
643
  }
795
644
  }
796
645
  }
797
- // Connect to a device via CDP WebSocket
798
646
  export async function connectToDevice(device, port, options = {}) {
799
647
  const { isReconnection = false, reconnectionConfig = DEFAULT_RECONNECTION_CONFIG } = options;
800
648
  return new Promise(async (resolve, reject) => {
801
649
  const appKey = `${port}-${device.id}`;
802
- // Check if already connected with a valid WebSocket
803
650
  const existingApp = connectedApps.get(appKey);
804
651
  if (existingApp) {
805
652
  if (existingApp.ws.readyState === WebSocket.OPEN) {
806
- // Verify WebSocket is actually alive with a ping/pong check.
807
- // readyState can show OPEN even when the remote end is gone
808
- // (TCP hasn't timed out yet, especially after Metro restart).
809
653
  const isAlive = await new Promise((pongResolve) => {
810
654
  const timeout = setTimeout(() => pongResolve(false), 2000);
811
655
  existingApp.ws.once("pong", () => {
@@ -824,26 +668,22 @@ export async function connectToDevice(device, port, options = {}) {
824
668
  resolve(`Already connected to ${device.title}`);
825
669
  return;
826
670
  }
827
- // Ping failed — connection is dead, clean up
828
671
  console.error(`[execbro] Existing connection to ${device.title} failed liveness check (no pong), reconnecting`);
829
672
  recordConnectionEvent("liveness-failed", appKey, device.title, "no pong within 2000ms");
830
673
  reconnectionSuppressed.add(appKey);
831
674
  try {
832
675
  existingApp.ws.terminate();
833
676
  }
834
- catch { /* ignore */ }
677
+ catch { }
835
678
  connectedApps.delete(appKey);
836
679
  clearLastCDPMessageTime(appKey);
837
680
  clearActiveSimulatorIfSource(appKey);
838
681
  }
839
682
  else {
840
- // WebSocket exists but not OPEN - clean up stale entry
841
683
  console.error(`[execbro] Cleaning up stale connection for ${device.title} (state: ${getWebSocketStateName(existingApp.ws.readyState)})`);
842
684
  connectedApps.delete(appKey);
843
685
  }
844
686
  }
845
- // Skip if this device is already connected on a different port
846
- // (Metro's /json advertises all devices it can see, even ones built by other Metro servers)
847
687
  const deviceName = device.deviceName || device.title;
848
688
  for (const [existingKey, existingApp] of connectedApps.entries()) {
849
689
  if (existingKey !== appKey && existingApp.ws.readyState === WebSocket.OPEN) {
@@ -854,15 +694,12 @@ export async function connectToDevice(device, port, options = {}) {
854
694
  }
855
695
  }
856
696
  }
857
- // Prevent concurrent connection attempts to the same device
858
697
  if (connectionLocks.has(appKey)) {
859
698
  resolve(`Connection already in progress for ${device.title}`);
860
699
  return;
861
700
  }
862
701
  connectionLocks.add(appKey);
863
- // Cancel any pending reconnection timer for this appKey
864
702
  cancelReconnectionTimer(appKey);
865
- // Save connection metadata for potential reconnection
866
703
  saveConnectionMetadata(appKey, {
867
704
  port,
868
705
  deviceInfo: device,
@@ -870,53 +707,34 @@ export async function connectToDevice(device, port, options = {}) {
870
707
  });
871
708
  try {
872
709
  const ws = await createWebSocketWithOriginFallback(device.webSocketDebuggerUrl);
873
- // Register close/error handlers IMMEDIATELY — before any async work
874
- // below (probeCdpAlive, Runtime.enable, simctl/adb lookups). Previously
875
- // they were attached only at the end of the setup block, so any close
876
- // event during that window was silently swallowed: the entry stayed
877
- // in connectedApps, readyState flipped to CLOSED, but no reconnect
878
- // was scheduled and no `closed` event was recorded.
879
710
  let pingInterval = null;
880
711
  let staleRejected = false;
881
712
  const handleClose = () => {
882
713
  if (staleRejected)
883
- return; // probe-fail path handles its own cleanup
714
+ return;
884
715
  if (pingInterval) {
885
716
  clearInterval(pingInterval);
886
717
  pingInterval = null;
887
718
  }
888
- // Fail anything still in flight on this socket. Otherwise each
889
- // call parks until its own timeoutMs and then reports a generic
890
- // "Expression took too long", which classifies as logical and
891
- // never triggers auto-reconnect.
892
719
  const failedCount = failPendingExecutionsForSocket(ws, "WebSocket connection is not open.");
893
720
  if (failedCount > 0) {
894
721
  console.error(`[execbro] Failed ${failedCount} in-flight call(s) on closed socket for ${device.title}`);
895
722
  }
896
- // Release connection lock if still held
897
723
  connectionLocks.delete(appKey);
898
- // Stop mirroring — the socket is gone, so every tick would just
899
- // fail. No drain attempt here: there is nothing left to read.
900
724
  stopSdkMirrorPoller(device.deviceName || device.title || "unknown");
901
725
  connectedApps.delete(appKey);
902
726
  clearLastCDPMessageTime(appKey);
903
- // Clear active simulator UDID if this connection set it
904
727
  clearActiveSimulatorIfSource(appKey);
905
- // Keep log and network buffers across reconnections
906
- // They are only cleared on demand via clear_logs / clear_network tools
907
- // Check if connection was stable before resetting attempts
908
728
  const state = getConnectionState(appKey);
909
729
  let wasStable = false;
910
730
  if (state?.lastConnectedTime) {
911
731
  const connectionDuration = Date.now() - state.lastConnectedTime.getTime();
912
732
  wasStable = connectionDuration >= MIN_STABLE_CONNECTION_MS;
913
733
  if (wasStable) {
914
- // Connection was stable - reset attempts for fresh start
915
734
  updateConnectionState(appKey, { reconnectionAttempts: 0 });
916
735
  console.error(`[execbro] Connection was stable for ${Math.round(connectionDuration / 1000)}s, resetting reconnection attempts`);
917
736
  }
918
737
  }
919
- // Record the gap and trigger reconnection
920
738
  recordConnectionGap(appKey, "Connection closed");
921
739
  if (state) {
922
740
  updateConnectionState(appKey, {
@@ -928,11 +746,7 @@ export async function connectToDevice(device, port, options = {}) {
928
746
  const earlyPhase = state ? "" : " (before state init)";
929
747
  console.error(`[execbro] Disconnected from ${device.title}${earlyPhase}`);
930
748
  recordConnectionEvent("closed", appKey, device.title, `uptime ${formatDuration(uptimeMs)}${wasStable ? " (stable)" : " (unstable)"}${earlyPhase}`);
931
- // Schedule auto-reconnection if enabled (skip if intentionally disconnected)
932
749
  if (reconnectionConfig.enabled && !reconnectionSuppressed.has(appKey)) {
933
- // If close fired before initConnectionState, scheduleReconnection
934
- // would early-return (it requires existing state). Init it now
935
- // so the reconnect loop can still run.
936
750
  if (!state)
937
751
  initConnectionState(appKey);
938
752
  scheduleReconnection(appKey, reconnectionConfig);
@@ -950,10 +764,6 @@ export async function connectToDevice(device, port, options = {}) {
950
764
  ws.on("error", (error) => {
951
765
  console.error(`[execbro] WebSocket error for ${device.title}: ${error?.message || error}`);
952
766
  });
953
- // Verify the JS context is actually alive. Metro's /json can advertise
954
- // zombie CDP pages that complete the WS handshake but no longer execute.
955
- // A stale target here causes every downstream tool to time out or return
956
- // degenerate state (e.g. __DEV__ === false on a debug bundle).
957
767
  const alive = await probeCdpAlive(ws, PROBE_TIMEOUT_MS);
958
768
  if (!alive) {
959
769
  staleRejected = true;
@@ -962,41 +772,29 @@ export async function connectToDevice(device, port, options = {}) {
962
772
  try {
963
773
  ws.terminate();
964
774
  }
965
- catch { /* ignore */ }
775
+ catch { }
966
776
  clearConnectionMetadata(appKey);
967
777
  console.error(`[execbro] Rejecting stale CDP target for ${device.title} (no probe response)`);
968
778
  recordConnectionEvent("stale-target", appKey, device.title, `no probe response within ${PROBE_TIMEOUT_MS}ms${isReconnection ? " (during reconnect)" : ""}`);
969
779
  resolve(`Skipped ${device.deviceName || device.title} (stale CDP target — no response from JS context)`);
970
780
  return;
971
781
  }
972
- // Connection established — run setup
973
782
  connectionLocks.delete(appKey);
974
783
  connectedApps.set(appKey, { ws, deviceInfo: device, port, platform: "android" });
975
784
  markConnectionEstablished();
976
- // A new CDP target id under a device name we have buffered before
977
- // means the app process was replaced. Bump so pre-restart entries
978
- // stay addressable and readers can draw the boundary.
979
785
  const bufferKey = device.deviceName || device.title || "unknown";
980
786
  const previousTargetId = lastTargetIdByDevice.get(bufferKey);
981
787
  if (previousTargetId && previousTargetId !== device.id) {
982
788
  bumpEpoch(bufferKey);
983
789
  }
984
790
  lastTargetIdByDevice.set(bufferKey, device.id);
985
- // Mirror the in-app SDK buffers into ours so a hard app restart
986
- // does not take the only copy with it. No-op when the SDK is absent
987
- // or EXECBRO_DISABLE_SDK_MIRROR=1.
988
791
  startSdkMirrorPoller(device.deviceName);
989
- // Initialize or update connection state
990
- // Note: We do NOT reset reconnectionAttempts here - that happens
991
- // only when connection has been stable for MIN_STABLE_CONNECTION_MS
992
792
  if (isReconnection) {
993
793
  closeConnectionGap(appKey);
994
794
  updateConnectionState(appKey, {
995
795
  status: "connected",
996
796
  lastConnectedTime: new Date()
997
- // reconnectionAttempts NOT reset here - see ws.on("close") for stable connection check
998
797
  });
999
- // Reset context health for reconnection
1000
798
  initContextHealth(appKey);
1001
799
  console.error(`[execbro] Reconnected to ${device.title}`);
1002
800
  recordConnectionEvent("reconnect-success", appKey, device.title);
@@ -1007,26 +805,20 @@ export async function connectToDevice(device, port, options = {}) {
1007
805
  console.error(`[execbro] Connected to ${device.title}`);
1008
806
  recordConnectionEvent("connect-success", appKey, device.title);
1009
807
  }
1010
- // Enable Runtime domain to receive console messages
1011
808
  ws.send(JSON.stringify({
1012
809
  id: getNextMessageId(),
1013
810
  method: "Runtime.enable"
1014
811
  }));
1015
- // Also enable Log domain
1016
812
  ws.send(JSON.stringify({
1017
813
  id: getNextMessageId(),
1018
814
  method: "Log.enable"
1019
815
  }));
1020
- // Inject JS network interceptor (immediate capture, may fail if context not ready)
1021
816
  injectNetworkInterceptor(ws);
1022
- // Also try CDP Network.enable (takes priority if supported)
817
+ void import("./routeHistory.js")
818
+ .then((m) => m.installRouteHistory(device.deviceName))
819
+ .catch(() => { });
1023
820
  const networkEnableId = sendNetworkEnable(ws);
1024
821
  pendingNetworkEnableIds.add(networkEnableId);
1025
- // Resolve native identifiers from the device name in parallel:
1026
- // - iOS: simulator UDID via findSimulatorByName
1027
- // - Android: adb serial via getAdbIdForAvd (maps AVD name → emulator-NNNN)
1028
- // Both are best-effort; failures are swallowed. Identifiers enable
1029
- // automatic device scoping and power the registry-first resolver fast path.
1030
822
  if (device.deviceName) {
1031
823
  const [simulatorUdid, adbSerial] = await Promise.all([
1032
824
  findSimulatorByName(device.deviceName).catch(() => null),
@@ -1047,19 +839,11 @@ export async function connectToDevice(device, port, options = {}) {
1047
839
  }
1048
840
  }
1049
841
  }
1050
- // Fire-and-forget app detection (500ms delayed, non-blocking)
1051
842
  const appForDetection = connectedApps.get(appKey);
1052
843
  if (appForDetection) {
1053
844
  scheduleAppDetection(appForDetection);
1054
845
  }
1055
- // Start periodic SDK probe. When __RN_AI_DEVTOOLS__ is detected,
1056
- // app.sdkPresent flips to true and CDP/JS-interceptor buffer
1057
- // writes are suppressed (SDK becomes the single source).
1058
846
  startSdkProbeLoop(ws, appKey);
1059
- // Start WebSocket ping/pong keepalive to detect dead connections
1060
- // (especially important for physical devices over Wi-Fi).
1061
- // pingInterval was declared up-top so the early-registered close
1062
- // handler can clear it; close/error handlers are already attached.
1063
847
  let pongReceived = true;
1064
848
  pingInterval = setInterval(() => {
1065
849
  if (!pongReceived) {
@@ -1083,13 +867,11 @@ export async function connectToDevice(device, port, options = {}) {
1083
867
  handleCDPMessage(message, device, ws);
1084
868
  }
1085
869
  catch {
1086
- // Ignore non-JSON messages
1087
870
  }
1088
871
  });
1089
872
  resolve(`Connected to ${device.title} (${device.deviceName})`);
1090
873
  }
1091
874
  catch (error) {
1092
- // Connection failed (both with and without Origin header)
1093
875
  connectionLocks.delete(appKey);
1094
876
  cancelReconnectionTimer(appKey);
1095
877
  connectedApps.delete(appKey);
@@ -1105,9 +887,6 @@ export async function connectToDevice(device, port, options = {}) {
1105
887
  }
1106
888
  });
1107
889
  }
1108
- /**
1109
- * Schedule a reconnection attempt with exponential backoff
1110
- */
1111
890
  function scheduleReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG) {
1112
891
  const state = getConnectionState(appKey);
1113
892
  if (!state)
@@ -1133,9 +912,6 @@ function scheduleReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG) {
1133
912
  }, delay);
1134
913
  saveReconnectionTimer(appKey, timer);
1135
914
  }
1136
- /**
1137
- * Attempt to reconnect to a previously connected device
1138
- */
1139
915
  async function attemptReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG) {
1140
916
  const metadata = getConnectionMetadata(appKey);
1141
917
  if (!metadata) {
@@ -1145,7 +921,6 @@ async function attemptReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG)
1145
921
  }
1146
922
  const deviceTitle = metadata.deviceInfo.title;
1147
923
  recordConnectionEvent("reconnect-attempt", appKey, deviceTitle);
1148
- // Quick check: is Metro even running on this port?
1149
924
  const { scanMetroPorts } = await import("./metro.js");
1150
925
  const openPorts = await scanMetroPorts(metadata.port, metadata.port);
1151
926
  if (openPorts.length === 0) {
@@ -1155,15 +930,12 @@ async function attemptReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG)
1155
930
  return false;
1156
931
  }
1157
932
  try {
1158
- // Re-fetch devices to get fresh WebSocket URL (may have changed)
1159
933
  const devices = await fetchDevices(metadata.port);
1160
- // Try to find the same device first, otherwise select main device
1161
934
  const device = devices.find(d => d.id === metadata.deviceInfo.id)
1162
935
  || selectMainDevice(devices);
1163
936
  if (!device) {
1164
937
  console.error(`[execbro] Device no longer available for ${appKey}`);
1165
938
  recordConnectionEvent("reconnect-failed", appKey, deviceTitle, "device no longer advertised by Metro");
1166
- // Schedule next attempt
1167
939
  scheduleReconnection(appKey, config);
1168
940
  return false;
1169
941
  }
@@ -1174,12 +946,10 @@ async function attemptReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG)
1174
946
  const msg = error instanceof Error ? error.message : String(error);
1175
947
  console.error(`[execbro] Reconnection failed: ${msg}`);
1176
948
  recordConnectionEvent("reconnect-failed", appKey, deviceTitle, msg);
1177
- // Schedule next attempt
1178
949
  scheduleReconnection(appKey, config);
1179
950
  return false;
1180
951
  }
1181
952
  }
1182
- // Get list of connected apps
1183
953
  export function getConnectedApps() {
1184
954
  return Array.from(connectedApps.entries()).map(([key, app]) => ({
1185
955
  key,
@@ -1187,24 +957,16 @@ export function getConnectedApps() {
1187
957
  isConnected: app.ws.readyState === WebSocket.OPEN
1188
958
  }));
1189
959
  }
1190
- // Get first connected app with an OPEN WebSocket (or null if none)
1191
960
  export function getFirstConnectedApp() {
1192
- // Find first app with OPEN WebSocket, cleaning up stale entries
1193
961
  for (const [key, app] of connectedApps.entries()) {
1194
962
  if (app.ws.readyState === WebSocket.OPEN) {
1195
963
  return app;
1196
964
  }
1197
- // Clean up stale entry
1198
965
  console.error(`[execbro] Cleaning up stale connection in getFirstConnectedApp: ${key} (state: ${getWebSocketStateName(app.ws.readyState)})`);
1199
966
  connectedApps.delete(key);
1200
967
  }
1201
968
  return null;
1202
969
  }
1203
- /**
1204
- * Find the connected RN app running on a specific iOS simulator (by UDID).
1205
- * Returns null if no connected app matches — caller should skip any CDP-based
1206
- * enrichment to avoid pulling data from a different simulator's app.
1207
- */
1208
970
  export function getConnectedAppBySimulatorUdid(udid) {
1209
971
  for (const [key, app] of connectedApps.entries()) {
1210
972
  if (app.ws.readyState !== WebSocket.OPEN) {
@@ -1217,13 +979,6 @@ export function getConnectedAppBySimulatorUdid(udid) {
1217
979
  }
1218
980
  return null;
1219
981
  }
1220
- /**
1221
- * Find the connected RN app running on a specific Android device.
1222
- * Falls back to the sole connected Android app when `deviceId` is not provided
1223
- * or cannot be resolved via deviceName substring match — this keeps the common
1224
- * single-device case working while preventing cross-device data leakage when
1225
- * multiple Android apps are connected.
1226
- */
1227
982
  export function getConnectedAppByAndroidDeviceId(deviceId) {
1228
983
  const androidApps = [];
1229
984
  for (const [key, app] of connectedApps.entries()) {
@@ -1249,20 +1004,13 @@ export function getConnectedAppByAndroidDeviceId(deviceId) {
1249
1004
  }
1250
1005
  return null;
1251
1006
  }
1252
- /**
1253
- * Lowercase and strip separators (whitespace, `_`, `-`) so substring matches
1254
- * survive punctuation drift between caller input and the device's reported
1255
- * identifier (e.g. "SM_A356N" vs "SM-A356N - 15 - API 35").
1256
- */
1257
1007
  function normalizeDeviceId(value) {
1258
1008
  if (!value)
1259
1009
  return "";
1260
1010
  return value.toLowerCase().replace(/[\s_\-]+/g, "");
1261
1011
  }
1262
- /** Guess which platform a caller-supplied device string refers to. */
1263
1012
  function guessRequestedPlatform(device) {
1264
1013
  const lower = device.toLowerCase();
1265
- // Simulator UDIDs are uppercase UUIDs; adb serials are not.
1266
1014
  if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(lower))
1267
1015
  return "ios";
1268
1016
  if (/iphone|ipad|ipod|\bios\b|simulator/.test(lower))
@@ -1274,10 +1022,6 @@ function guessRequestedPlatform(device) {
1274
1022
  function deviceLabel(app) {
1275
1023
  return app.deviceInfo.deviceName || app.deviceInfo.title || "";
1276
1024
  }
1277
- /**
1278
- * Human-facing explanation for a `kind: "none"` / `kind: "ambiguous"` resolution.
1279
- * Kept next to the matcher so the message always reflects the matching rules.
1280
- */
1281
1025
  export function describeDeviceResolution(resolution) {
1282
1026
  if (resolution.kind === "ok")
1283
1027
  return "";
@@ -1293,9 +1037,6 @@ export function describeDeviceResolution(resolution) {
1293
1037
  return `No connected device matches "${device}". No devices are currently connected — run scan_metro to discover and connect to Metro servers.`;
1294
1038
  }
1295
1039
  const quoted = connected.map(a => `"${deviceLabel(a)}"`).join(", ");
1296
- // Cross-platform mismatch is the dominant shape of this failure: the agent
1297
- // passes a name from list_devices while only the other
1298
- // platform is attached to Metro. Saying so beats "retry with one of these".
1299
1040
  const requested = guessRequestedPlatform(device);
1300
1041
  const attachedPlatforms = new Set(connected.map(a => a.platform));
1301
1042
  if (requested && !attachedPlatforms.has(requested)) {
@@ -1311,10 +1052,6 @@ export function describeDeviceResolution(resolution) {
1311
1052
  }
1312
1053
  return `No connected device matches "${device}". Connected devices: ${quoted}. Retry with one of these names (substring match, case-insensitive), or run get_apps to list them.`;
1313
1054
  }
1314
- /**
1315
- * Non-throwing device matcher. Prefer this in call sites that can retry
1316
- * (auto-connect, rescan); use getConnectedAppByDevice when a miss is terminal.
1317
- */
1318
1055
  export function resolveConnectedAppByDevice(device) {
1319
1056
  const openApps = [];
1320
1057
  for (const [key, app] of connectedApps.entries()) {
@@ -1332,10 +1069,6 @@ export function resolveConnectedAppByDevice(device) {
1332
1069
  const normDevice = normalizeDeviceId(device);
1333
1070
  const matches = [];
1334
1071
  for (const app of openApps) {
1335
- // Match against deviceName + the underlying hardware identifiers, with
1336
- // separator-insensitive normalization so "SM_A356N" finds
1337
- // "SM-A356N - 15 - API 35" and "emulator-5554" finds the Android app
1338
- // attached to that serial.
1339
1072
  const haystacks = [
1340
1073
  normalizeDeviceId(deviceLabel(app)),
1341
1074
  normalizeDeviceId(app.simulatorUdid),
@@ -1345,8 +1078,6 @@ export function resolveConnectedAppByDevice(device) {
1345
1078
  matches.push(app);
1346
1079
  }
1347
1080
  }
1348
- // Prefer exact (case-insensitive) match when present — disambiguates
1349
- // "iPhone 17 Pro" from "iPhone 17 Pro Max" when both are connected.
1350
1081
  const exact = matches.find(a => deviceLabel(a).toLowerCase() === lowerDevice);
1351
1082
  if (exact)
1352
1083
  return { kind: "ok", app: exact };
@@ -1360,8 +1091,6 @@ export function getConnectedAppByDevice(device) {
1360
1091
  const resolution = resolveConnectedAppByDevice(device);
1361
1092
  if (resolution.kind === "ok")
1362
1093
  return resolution.app;
1363
- // No device argument: a miss just means "nothing connected" — callers
1364
- // handle null themselves (historically getFirstConnectedApp's contract).
1365
1094
  if (!device)
1366
1095
  return null;
1367
1096
  const context = resolution.kind === "ambiguous"
@@ -1369,7 +1098,6 @@ export function getConnectedAppByDevice(device) {
1369
1098
  : resolution.connected.length > 0 ? "device_mismatch" : "no_devices_connected";
1370
1099
  throw new UserInputError(describeDeviceResolution(resolution), context);
1371
1100
  }
1372
- // Check if any app is connected with an OPEN WebSocket
1373
1101
  export function hasConnectedApp() {
1374
1102
  for (const [, app] of connectedApps.entries()) {
1375
1103
  if (app.ws.readyState === WebSocket.OPEN) {
@@ -1378,10 +1106,6 @@ export function hasConnectedApp() {
1378
1106
  }
1379
1107
  return false;
1380
1108
  }
1381
- /**
1382
- * Run a quick health check to verify the page context is responsive
1383
- * Returns true if the context can execute code, false otherwise
1384
- */
1385
1109
  export async function runQuickHealthCheck(app) {
1386
1110
  const HEALTH_CHECK_TIMEOUT = 2000;
1387
1111
  const messageId = getNextMessageId();
@@ -1394,7 +1118,6 @@ export async function runQuickHealthCheck(app) {
1394
1118
  resolve: (result) => {
1395
1119
  clearTimeout(timeoutId);
1396
1120
  pendingExecutions.delete(messageId);
1397
- // Update context health tracking
1398
1121
  const appKey = findAppKeyForDevice(app.deviceInfo);
1399
1122
  if (appKey) {
1400
1123
  updateContextHealth(appKey, {
@@ -1422,10 +1145,6 @@ export async function runQuickHealthCheck(app) {
1422
1145
  });
1423
1146
  }
1424
1147
  const LOG_PIPELINE_MARKER_PREFIX = "__rn_devtools_health_";
1425
- /**
1426
- * Inject a sentinel console.log via CDP and wait for it to appear in the log buffer.
1427
- * Returns true if the marker arrives within TIMEOUT_MS.
1428
- */
1429
1148
  async function sendAndWaitForMarker(app, buffer) {
1430
1149
  const marker = `${LOG_PIPELINE_MARKER_PREFIX}${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1431
1150
  const TIMEOUT_MS = 3000;
@@ -1439,7 +1158,6 @@ async function sendAndWaitForMarker(app, buffer) {
1439
1158
  catch {
1440
1159
  return false;
1441
1160
  }
1442
- // Poll the buffer for the marker (check every 50ms, up to TIMEOUT_MS)
1443
1161
  const startTime = Date.now();
1444
1162
  while (Date.now() - startTime < TIMEOUT_MS) {
1445
1163
  if (buffer.getAll().some(entry => entry.message.includes(marker))) {
@@ -1450,45 +1168,29 @@ async function sendAndWaitForMarker(app, buffer) {
1450
1168
  }
1451
1169
  return false;
1452
1170
  }
1453
- /**
1454
- * End-to-end log pipeline verification with automatic recovery.
1455
- *
1456
- * 1. Injects a sentinel console.log via CDP and verifies it appears in the buffer.
1457
- * 2. If the marker doesn't arrive, attempts recovery:
1458
- * a. Re-sends Runtime.enable + Log.enable (fixes missing domain subscription)
1459
- * b. Retries the marker once
1460
- * 3. If still broken, force-reconnects via ensureConnection and retries.
1461
- *
1462
- * Returns { ok, recovered, message } — the caller decides what to show.
1463
- */
1464
1171
  export async function verifyLogPipeline(app) {
1465
1172
  const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
1466
1173
  const buffer = getLogBuffer(deviceName);
1467
- // --- Attempt 1: quick check ---
1468
1174
  if (await sendAndWaitForMarker(app, buffer)) {
1469
1175
  return { ok: true, recovered: false, message: null };
1470
1176
  }
1471
- // --- Attempt 2: re-enable Runtime/Log domains and retry ---
1472
1177
  try {
1473
1178
  app.ws.send(JSON.stringify({ id: getNextMessageId(), method: "Runtime.enable" }));
1474
1179
  app.ws.send(JSON.stringify({ id: getNextMessageId(), method: "Log.enable" }));
1475
1180
  }
1476
1181
  catch {
1477
- // WS send failed — fall through to reconnect
1478
1182
  }
1479
- // Brief pause for domain activation
1480
1183
  await new Promise(resolve => setTimeout(resolve, 300));
1481
1184
  if (await sendAndWaitForMarker(app, buffer)) {
1482
1185
  return { ok: true, recovered: true, message: "[CONNECTION] Log pipeline was stale — re-enabled CDP domains. Logs are flowing again." };
1483
1186
  }
1484
- // --- Attempt 3: force reconnect and retry ---
1485
1187
  const appKey = `${app.port}-${app.deviceInfo.id}`;
1486
1188
  const port = app.port;
1487
1189
  cancelReconnectionTimer(appKey);
1488
1190
  try {
1489
1191
  app.ws.close();
1490
1192
  }
1491
- catch { /* ignore */ }
1193
+ catch { }
1492
1194
  connectedApps.delete(appKey);
1493
1195
  const result = await ensureConnection({ port, forceRefresh: false, healthCheck: true });
1494
1196
  if (!result.connected || !result.healthCheckPassed) {
@@ -1498,9 +1200,7 @@ export async function verifyLogPipeline(app) {
1498
1200
  message: "[CONNECTION] Log pipeline broken and reconnection failed. Ensure the app is running, then call scan_metro.",
1499
1201
  };
1500
1202
  }
1501
- // Wait for the new connection to settle
1502
1203
  await new Promise(resolve => setTimeout(resolve, 500));
1503
- // Get the fresh app reference after reconnection
1504
1204
  const freshApp = getFirstConnectedApp();
1505
1205
  if (!freshApp) {
1506
1206
  return {
@@ -1524,43 +1224,29 @@ export async function verifyLogPipeline(app) {
1524
1224
  message: "[WARNING] Log pipeline verification failed after reconnection. Console events are not reaching the buffer. Try reload_app or scan_metro.",
1525
1225
  };
1526
1226
  }
1527
- /**
1528
- * Check if a log message is a health check marker (for filtering from output).
1529
- */
1530
1227
  export function isHealthCheckMarker(message) {
1531
1228
  return message.includes(LOG_PIPELINE_MARKER_PREFIX);
1532
1229
  }
1533
- /**
1534
- * Find the first available Metro port
1535
- */
1536
1230
  async function findFirstMetroPort() {
1537
1231
  const ports = await scanMetroPorts();
1538
1232
  return ports.length > 0 ? ports[0] : null;
1539
1233
  }
1540
- /**
1541
- * Ensure a healthy connection to a React Native app
1542
- * This will verify or establish a connection, optionally running a health check
1543
- */
1544
1234
  export async function ensureConnection(options = {}) {
1545
1235
  const { port, healthCheck = true, forceRefresh = false } = options;
1546
1236
  let wasReconnected = false;
1547
- // forceRefresh: close every currently-connected app, then fall through to
1548
- // single-device fresh connect. Full multi-device re-discovery is scan_metro's
1549
- // job — ensureConnection only fresh-connects one when nothing is open.
1550
1237
  if (forceRefresh) {
1551
1238
  for (const { key, app } of getConnectedApps()) {
1552
1239
  cancelReconnectionTimer(key);
1553
1240
  try {
1554
1241
  app.ws.close();
1555
1242
  }
1556
- catch { /* ignore */ }
1243
+ catch { }
1557
1244
  connectedApps.delete(key);
1558
1245
  }
1559
1246
  }
1560
1247
  let openApps = getConnectedApps()
1561
1248
  .filter(a => a.isConnected)
1562
1249
  .map(a => a.app);
1563
- // Nothing connected — establish a single-device connection from scratch.
1564
1250
  if (openApps.length === 0) {
1565
1251
  const targetPort = port ?? await findFirstMetroPort();
1566
1252
  if (!targetPort) {
@@ -1584,10 +1270,6 @@ export async function ensureConnection(options = {}) {
1584
1270
  };
1585
1271
  }
1586
1272
  try {
1587
- // Clear any prior reconnection suppression — without this a stale
1588
- // suppression entry from a previous disconnect_metro keeps the
1589
- // close-handler from auto-recovering if Metro drops the WS just
1590
- // after probe.
1591
1273
  clearReconnectionSuppression();
1592
1274
  const connectResult = await connectToDevice(mainDevice, targetPort);
1593
1275
  wasReconnected = true;
@@ -1600,7 +1282,6 @@ export async function ensureConnection(options = {}) {
1600
1282
  error: `${connectResult}. The CDP page advertised by Metro is no longer responsive — restart the React Native app, then retry.`,
1601
1283
  };
1602
1284
  }
1603
- // Race window: settle briefly and retry getting the open app list.
1604
1285
  for (let i = 0; i < 5 && openApps.length === 0; i++) {
1605
1286
  if (i > 0)
1606
1287
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -1636,8 +1317,6 @@ export async function ensureConnection(options = {}) {
1636
1317
  error: "Connection succeeded but app is not available",
1637
1318
  };
1638
1319
  }
1639
- // Per-device health-check; reconnect failed devices individually so a dead
1640
- // Android doesn't take down a healthy iOS report and vice versa.
1641
1320
  const perApp = [];
1642
1321
  for (const candidate of openApps) {
1643
1322
  let app = candidate;
@@ -1653,7 +1332,7 @@ export async function ensureConnection(options = {}) {
1653
1332
  try {
1654
1333
  app.ws.close();
1655
1334
  }
1656
- catch { /* ignore */ }
1335
+ catch { }
1657
1336
  connectedApps.delete(appKey);
1658
1337
  const devices = await fetchDevices(appPort);
1659
1338
  const mainDevice = selectMainDevice(devices);
@@ -1716,7 +1395,6 @@ export async function ensureConnection(options = {}) {
1716
1395
  }
1717
1396
  export function getPassiveConnectionStatus(targetAppKey) {
1718
1397
  if (targetAppKey) {
1719
- // Check a specific device's connection
1720
1398
  const app = connectedApps.get(targetAppKey);
1721
1399
  if (!app || app.ws.readyState !== WebSocket.OPEN) {
1722
1400
  return { connected: false, needsPing: false, reason: "no_connection" };
@@ -1735,7 +1413,6 @@ export function getPassiveConnectionStatus(targetAppKey) {
1735
1413
  }
1736
1414
  return { connected: true, needsPing: false, reason: "ok" };
1737
1415
  }
1738
- // Default: check first connected app (backwards compatible)
1739
1416
  if (!hasConnectedApp()) {
1740
1417
  return { connected: false, needsPing: false, reason: "no_connection" };
1741
1418
  }
@@ -1758,7 +1435,6 @@ export function getPassiveConnectionStatus(targetAppKey) {
1758
1435
  return { connected: true, needsPing: false, reason: "ok" };
1759
1436
  }
1760
1437
  export async function checkAndEnsureConnection(device) {
1761
- // Resolve targeted device to appKey if specified
1762
1438
  let targetAppKey;
1763
1439
  let targetApp = null;
1764
1440
  if (device) {
@@ -1769,7 +1445,6 @@ export async function checkAndEnsureConnection(device) {
1769
1445
  }
1770
1446
  }
1771
1447
  catch {
1772
- // Device not found — fall through to reconnection
1773
1448
  }
1774
1449
  }
1775
1450
  const passive = getPassiveConnectionStatus(targetAppKey);