@xenon-device-management/xenon 2.0.0 → 2.2.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 (289) hide show
  1. package/README.md +2 -1
  2. package/lib/package.json +3 -4
  3. package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-XpewX5nn.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-BQDv3ZCC.js} +1 -1
  5. package/lib/public/assets/BugReportButton-C7OmS61B.js +10 -0
  6. package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-wl3l2Zlt.js} +1 -1
  7. package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-fPxujqqE.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-5bCutWrs.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-owsBiDT1.js} +1 -1
  10. package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-DFhqzPSt.js} +1 -1
  11. package/lib/public/assets/{Modal-CceoMY7D.js → Modal-Bw-a-E-e.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-WtByf5bC.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-FpWWFNgT.js} +1 -1
  14. package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-Jzn9c7o8.js} +1 -1
  15. package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-BR7nOhpT.js} +1 -1
  16. package/lib/public/assets/{Table-LV7zFyZg.js → Table-B_Va3V05.js} +1 -1
  17. package/lib/public/assets/{activity-D17TUloi.js → activity-DxRFWvkN.js} +1 -1
  18. package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-CQf-9_V6.js} +1 -1
  19. package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-SoE1Kd7N.js} +1 -1
  20. package/lib/public/assets/{apps-Dbnzd0fW.js → apps-CgGXTlgn.js} +1 -1
  21. package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-uQ6WIwiC.js} +1 -1
  22. package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-C9RyfzXS.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-DFHi_8yG.js} +1 -1
  24. package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-Bii1t4qc.js} +2 -2
  25. package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-Bnv8Z9hi.js} +1 -1
  26. package/lib/public/assets/{button-xtAhYR-f.js → button-_Y78wXiP.js} +1 -1
  27. package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-D4MqSOc2.js} +1 -1
  28. package/lib/public/assets/{check-CyuEoKkd.js → check-uGHCXe-L.js} +1 -1
  29. package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-Dy1OqpzB.js} +1 -1
  30. package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-BUsldEli.js} +1 -1
  31. package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-D3Qq0tKp.js} +1 -1
  32. package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-B4IOD1DE.js} +1 -1
  33. package/lib/public/assets/{clock-Cd-t7xpe.js → clock-BfYfzvSg.js} +1 -1
  34. package/lib/public/assets/{copy-C6zerX3I.js → copy-DZwYE5Va.js} +1 -1
  35. package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CRG4Utt5.js} +3 -3
  36. package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
  37. package/lib/public/assets/{download-x9WP7mcL.js → download-CCDYo_TN.js} +1 -1
  38. package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-DuLCmVFb.js} +1 -1
  39. package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-CtKNPqzb.js} +1 -1
  40. package/lib/public/assets/{index-BR1wTIPC.js → index-3uxl3o7K.js} +43 -43
  41. package/lib/public/assets/{index-BvH_ekRy.js → index-Zy1Op5C1.js} +1 -1
  42. package/lib/public/assets/{input-DejcCiTI.js → input-CzTWvUXZ.js} +1 -1
  43. package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CI-nYDFM.js} +1 -1
  44. package/lib/public/assets/{layers-BKqYLiHE.js → layers-YZZGFyTX.js} +1 -1
  45. package/lib/public/assets/{lock-LFsFhc17.js → lock-L8tzWgDo.js} +1 -1
  46. package/lib/public/assets/{login-x2pkzt-t.js → login-DBbX-fu8.js} +1 -1
  47. package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-AJwkOIsE.js} +1 -1
  48. package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-Di_66F2m.js} +1 -1
  49. package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-Cnab2zdg.js} +1 -1
  50. package/lib/public/assets/{network-Z_zBhpsD.js → network-DiSumY-1.js} +1 -1
  51. package/lib/public/assets/{overview-BY5uv08S.js → overview-CwE4gXl5.js} +1 -1
  52. package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CfR4Sm9V.js} +1 -1
  53. package/lib/public/assets/{plus-BTD0dnQ0.js → plus-DOS8P9CQ.js} +1 -1
  54. package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-BYpHwQIH.js} +1 -1
  55. package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BRVJU_qV.js} +1 -1
  56. package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Bi3JvAFZ.js} +1 -1
  57. package/lib/public/assets/{select-Uc9E5pye.js → select-Cqx7pEou.js} +1 -1
  58. package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C0DsncKj.js} +1 -1
  59. package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CAhpdilF.js} +1 -1
  60. package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CrCVeW2m.js} +1 -1
  61. package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-F_hwYqEt.js} +1 -1
  62. package/lib/public/assets/{settings-CkSWiZq8.js → settings-C-s5pypL.js} +1 -1
  63. package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-DY5uzv6A.js} +1 -1
  64. package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-Dli7gwyg.js} +1 -1
  65. package/lib/public/assets/{teams-PpoEQz31.js → teams-ui4IhHNC.js} +1 -1
  66. package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV4hSmXJ.js} +1 -1
  67. package/lib/public/assets/{upload-C3i7sAZl.js → upload-DkGy_J-L.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-BFtMqQOh.js} +1 -1
  69. package/lib/public/assets/{users-Drd7ifJx.js → users-BU0nXIVe.js} +1 -1
  70. package/lib/public/assets/{users-CEJ3fC78.js → users-Nb_rzRmF.js} +1 -1
  71. package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-CUNzD2kH.js} +1 -1
  72. package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-Ca3UFXZe.js} +1 -1
  73. package/lib/public/assets/{zap-4yN8Jdfw.js → zap-B2bH2vKX.js} +1 -1
  74. package/lib/public/index.html +1 -1
  75. package/lib/src/InternalHttpClient.js +5 -1
  76. package/lib/src/XenonCapabilityManager.js +15 -1
  77. package/lib/src/app/appiumBasePath.js +32 -0
  78. package/lib/src/app/index.js +12 -0
  79. package/lib/src/app/insertBeforeRoutes.js +24 -4
  80. package/lib/src/app/registerCommandAuth.js +152 -33
  81. package/lib/src/app/routers/control.js +11 -104
  82. package/lib/src/app/routers/grid.js +28 -8
  83. package/lib/src/app/routers/nodePhoneControl.js +301 -0
  84. package/lib/src/app/ws/h264StreamWs.js +43 -12
  85. package/lib/src/app/ws/logcatWs.js +48 -14
  86. package/lib/src/app/ws/nodeSocketRelay.js +167 -0
  87. package/lib/src/app/ws/upgradeRouter.js +215 -0
  88. package/lib/src/dashboard/event-manager.js +7 -6
  89. package/lib/src/data-service/device-service.js +152 -49
  90. package/lib/src/data-service/device-store.js +92 -10
  91. package/lib/src/data-service/deviceClaims.js +116 -0
  92. package/lib/src/data-service/deviceFieldOwners.js +32 -10
  93. package/lib/src/data-service/prisma-store.js +102 -31
  94. package/lib/src/device-managers/AndroidDeviceManager.js +40 -42
  95. package/lib/src/device-managers/HealthMonitorService.js +17 -2
  96. package/lib/src/device-managers/IOSDeviceManager.js +5 -1
  97. package/lib/src/device-managers/NodeDevices.js +7 -2
  98. package/lib/src/device-managers/android/AndroidH264StreamService.js +13 -5
  99. package/lib/src/device-managers/android/AndroidStreamService.js +9 -6
  100. package/lib/src/device-managers/android/previewHold.js +2 -2
  101. package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
  102. package/lib/src/device-managers/ios/IOSStreamService.js +162 -40
  103. package/lib/src/device-managers/localDeviceHosts.js +86 -0
  104. package/lib/src/device-managers/ownDeviceRow.js +38 -0
  105. package/lib/src/device-utils.js +78 -24
  106. package/lib/src/gateway/createHandoff.js +73 -0
  107. package/lib/src/gateway/defaultGateway.js +80 -0
  108. package/lib/src/gateway/forwardToNode.js +147 -0
  109. package/lib/src/gateway/hubSessionToken.js +356 -0
  110. package/lib/src/gateway/internalCall.js +90 -0
  111. package/lib/src/gateway/nodeSessionStatus.js +140 -0
  112. package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
  113. package/lib/src/gateway/sessionCreate.js +136 -0
  114. package/lib/src/gateway/sessionGateway.js +201 -0
  115. package/lib/src/gateway/sessionLocator.js +100 -0
  116. package/lib/src/generated/client/edge.js +7 -4
  117. package/lib/src/generated/client/index-browser.js +4 -1
  118. package/lib/src/generated/client/index.d.ts +112 -1
  119. package/lib/src/generated/client/index.js +7 -4
  120. package/lib/src/generated/client/package.json +1 -1
  121. package/lib/src/generated/client/schema.prisma +9 -0
  122. package/lib/src/generated/client/wasm.js +4 -1
  123. package/lib/src/helpers/index.js +5 -1
  124. package/lib/src/logger.js +5 -0
  125. package/lib/src/middleware/authMiddleware.js +80 -0
  126. package/lib/src/middleware/commandAuth.js +31 -17
  127. package/lib/src/middleware/csrfMiddleware.js +3 -2
  128. package/lib/src/middleware/deviceAccessGuard.js +4 -1
  129. package/lib/src/middleware/sessionListingFilter.js +153 -0
  130. package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
  131. package/lib/src/plugin.js +27 -1
  132. package/lib/src/prisma.js +2 -1
  133. package/lib/src/services/DeviceReconciler.js +5 -2
  134. package/lib/src/services/OrphanSweeper.js +12 -19
  135. package/lib/src/services/PortAllocator.js +75 -16
  136. package/lib/src/services/ServerManager.js +63 -30
  137. package/lib/src/services/SessionLifecycleService.js +335 -76
  138. package/lib/src/services/ShutdownCoordinator.js +7 -1
  139. package/lib/src/services/SocketServer.js +11 -2
  140. package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
  141. package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
  142. package/lib/src/services/device-access/ticketActorAccess.js +3 -1
  143. package/lib/src/services/lease/LeaseOrphanSweeper.js +34 -6
  144. package/lib/src/services/lease/LeaseService.js +19 -5
  145. package/lib/src/services/session/nodeCreateCaps.js +55 -0
  146. package/lib/src/services/session/sessionCredentials.js +7 -59
  147. package/lib/src/sessions/CloudSession.js +12 -0
  148. package/lib/src/sessions/LocalSession.js +52 -26
  149. package/lib/src/sessions/RemoteSession.js +116 -9
  150. package/lib/src/sessions/SessionManager.js +49 -6
  151. package/lib/src/sessions/appiumUmbrella.js +32 -0
  152. package/lib/test/helpers/appium-umbrella.js +84 -0
  153. package/lib/test/helpers/fake-device-table.js +97 -0
  154. package/lib/test/helpers/loki-stores.js +41 -0
  155. package/lib/test/helpers/loopbackRequest.js +77 -0
  156. package/lib/test/helpers/own-node-id.js +29 -0
  157. package/lib/test/helpers/scratch-database.js +100 -0
  158. package/lib/test/helpers/scratch-port-leases.js +119 -0
  159. package/lib/test/helpers/test-container.js +35 -27
  160. package/lib/test/integration/androidDevices.spec.js +5 -4
  161. package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
  162. package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
  163. package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
  164. package/lib/test/setup/scratch-default-database.js +32 -0
  165. package/lib/test/unit/AndroidDeviceManager.spec.js +56 -1
  166. package/lib/test/unit/DeviceModel.spec.js +9 -9
  167. package/lib/test/unit/HealingCascade.test.js +5 -1
  168. package/lib/test/unit/OrphanSweeper.test.js +17 -13
  169. package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
  170. package/lib/test/unit/StreamTicketService.spec.js +4 -1
  171. package/lib/test/unit/TokenEndpoint.spec.js +17 -3
  172. package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
  173. package/lib/test/unit/android-h264-service.spec.js +24 -0
  174. package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
  175. package/lib/test/unit/apps-team-visibility.spec.js +16 -16
  176. package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
  177. package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
  178. package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
  179. package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
  180. package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
  181. package/lib/test/unit/authMiddleware.test.js +3 -0
  182. package/lib/test/unit/bug-report/route.spec.js +9 -9
  183. package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
  184. package/lib/test/unit/clipboard-route.spec.js +22 -8
  185. package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
  186. package/lib/test/unit/command-auth.spec.js +31 -31
  187. package/lib/test/unit/control-device-lookup.spec.js +9 -9
  188. package/lib/test/unit/control-forward-decision.spec.js +115 -0
  189. package/lib/test/unit/control-lookup-failure.spec.js +3 -3
  190. package/lib/test/unit/create-error-message.spec.js +43 -0
  191. package/lib/test/unit/create-handoff.spec.js +86 -0
  192. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  193. package/lib/test/unit/device-access-guard.spec.js +66 -28
  194. package/lib/test/unit/device-service.spec.js +4 -0
  195. package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
  196. package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
  197. package/lib/test/unit/device-team-guard.spec.js +20 -20
  198. package/lib/test/unit/device-utils.spec.js +8 -0
  199. package/lib/test/unit/extract-team-cap.spec.js +65 -0
  200. package/lib/test/unit/grid-admin-only.spec.js +3 -3
  201. package/lib/test/unit/healing-tier-gate.spec.js +5 -1
  202. package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
  203. package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
  204. package/lib/test/unit/helpers.spec.js +27 -0
  205. package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
  206. package/lib/test/unit/hub-create-once.spec.js +190 -0
  207. package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
  208. package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
  209. package/lib/test/unit/hub-node-control-forward.spec.js +608 -0
  210. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
  211. package/lib/test/unit/hub-node-race.spec.js +426 -0
  212. package/lib/test/unit/hub-node-same-host.spec.js +356 -0
  213. package/lib/test/unit/hub-register-removal.spec.js +165 -0
  214. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
  215. package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
  216. package/lib/test/unit/hub-session-token.spec.js +265 -0
  217. package/lib/test/unit/insert-before-routes.spec.js +5 -5
  218. package/lib/test/unit/install-repository-app-team.spec.js +6 -2
  219. package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
  220. package/lib/test/unit/internal-call.spec.js +181 -0
  221. package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
  222. package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
  223. package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
  224. package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
  225. package/lib/test/unit/is-xenon-running.spec.js +69 -0
  226. package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
  227. package/lib/test/unit/lease/LeaseService.spec.js +28 -1
  228. package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
  229. package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +107 -0
  230. package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
  231. package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
  232. package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
  233. package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
  234. package/lib/test/unit/logcat-ws.spec.js +12 -1
  235. package/lib/test/unit/loopback-request.spec.js +81 -0
  236. package/lib/test/unit/mark-node-busy-loki.spec.js +53 -0
  237. package/lib/test/unit/node-create-caps.spec.js +54 -0
  238. package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
  239. package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
  240. package/lib/test/unit/node-phone-control.spec.js +131 -0
  241. package/lib/test/unit/node-socket-relay.spec.js +357 -0
  242. package/lib/test/unit/omni-vision.spec.js +7 -3
  243. package/lib/test/unit/plugin.spec.js +45 -4
  244. package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
  245. package/lib/test/unit/profile-router.test.js +11 -11
  246. package/lib/test/unit/queue-requester.spec.js +5 -0
  247. package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
  248. package/lib/test/unit/queue-visibility.spec.js +1 -1
  249. package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
  250. package/lib/test/unit/recording-instrumentation.spec.js +5 -0
  251. package/lib/test/unit/recording-source-ended.spec.js +5 -0
  252. package/lib/test/unit/recording-store.spec.js +9 -3
  253. package/lib/test/unit/recordings-library-routes.spec.js +51 -51
  254. package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
  255. package/lib/test/unit/remote-session-create.spec.js +130 -0
  256. package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
  257. package/lib/test/unit/reset-link.test.js +14 -14
  258. package/lib/test/unit/session-app-resolution.spec.js +14 -10
  259. package/lib/test/unit/session-assets.spec.js +9 -9
  260. package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
  261. package/lib/test/unit/session-create-layer.spec.js +255 -0
  262. package/lib/test/unit/session-credential-strip.spec.js +25 -9
  263. package/lib/test/unit/session-credentials.spec.js +0 -47
  264. package/lib/test/unit/session-listing-auth.spec.js +345 -0
  265. package/lib/test/unit/session-locator.spec.js +250 -0
  266. package/lib/test/unit/session-node-calls.spec.js +174 -0
  267. package/lib/test/unit/session-owner-resolver.spec.js +107 -0
  268. package/lib/test/unit/session-recovery-routing.spec.js +118 -0
  269. package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
  270. package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
  271. package/lib/test/unit/socketServer.bearer.spec.js +3 -1
  272. package/lib/test/unit/stream-leave-route.spec.js +19 -7
  273. package/lib/test/unit/stream-own-device-row.spec.js +164 -0
  274. package/lib/test/unit/stream-stop-ownership.spec.js +17 -5
  275. package/lib/test/unit/stream-stop-recording.spec.js +17 -5
  276. package/lib/test/unit/stream-ticket-identity.spec.js +26 -6
  277. package/lib/test/unit/stream-watchdog-timers.spec.js +46 -0
  278. package/lib/test/unit/upgrade-router.spec.js +446 -0
  279. package/lib/test/unit/users-router.test.js +11 -8
  280. package/lib/test/unit/verify-credential.spec.js +4 -1
  281. package/lib/test/unit/webdriver-info-route.spec.js +64 -0
  282. package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
  283. package/lib/tsconfig.tsbuildinfo +1 -1
  284. package/package.json +3 -4
  285. package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
  286. package/prisma/schema.prisma +9 -0
  287. package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
  288. package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
  289. package/lib/src/proxy/wd-command-proxy.js +0 -221
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ require("reflect-metadata");
49
+ const chai_1 = require("chai");
50
+ const sinon_1 = __importDefault(require("sinon"));
51
+ const typedi_1 = require("typedi");
52
+ const appium_ios_device_1 = require("appium-ios-device");
53
+ const simctlModule = __importStar(require("node-simctl"));
54
+ const IOSDiscoveryService_1 = require("../../src/device-managers/ios/IOSDiscoveryService");
55
+ const IOSDeviceManager_1 = __importDefault(require("../../src/device-managers/IOSDeviceManager"));
56
+ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
57
+ const device_store_1 = require("../../src/data-service/device-store");
58
+ const deviceUtils = __importStar(require("../../src/device-utils"));
59
+ const PluginContext_1 = require("../../src/PluginContext");
60
+ const PortAllocator_1 = require("../../src/services/PortAllocator");
61
+ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
62
+ /**
63
+ * A hub on :4724 and a node on :4725 on one Mac both see the same iPhone and
64
+ * the same simulators, and the hub's table holds the node's rows too. A row
65
+ * is a server's own only by udid AND host, as the Android discovery and
66
+ * localDeviceHosts have it. iOS discovery looked rows up by udid alone, so
67
+ * the hub could take the node's row for its own: its host, ports and busy
68
+ * state.
69
+ *
70
+ * Nothing real is asked: usbmux, go-ios, simctl and the store are fakes.
71
+ */
72
+ const IP = '192.168.0.104';
73
+ const HUB = `http://${IP}:4724`;
74
+ const NODE = `http://${IP}:4725`;
75
+ const IPHONE = 'test-iphone-00008150-shared';
76
+ const SIM = 'TEST-SIM-5A0C-shared';
77
+ function context(args, port) {
78
+ const ctx = new PluginContext_1.PluginContext();
79
+ ctx.setContext(Object.assign({}, IPluginArgs_1.DefaultPluginArgs, args), port, 'test-hub', '');
80
+ return ctx;
81
+ }
82
+ describe("iOS discovery takes only its own host's row for a udid", () => {
83
+ let rows;
84
+ const nodeRow = (udid, over = {}) => (Object.assign({ udid, host: NODE, platform: 'ios', realDevice: true, wdaLocalPort: 8200, mjpegServerPort: 9200, busy: true, session_id: 'node-session-1', screenWidth: '1320', screenHeight: '2868' }, over));
85
+ // resetTestContainer() (test/helpers/test-container.ts) stubs these two on
86
+ // the prototype for the rest of the process, so in a full run discovery
87
+ // never reaches them. Put the real ones on this instance.
88
+ const real = (fn) => { var _a; return (_a = fn.wrappedMethod) !== null && _a !== void 0 ? _a : fn; };
89
+ function hubDiscovery(connected = [IPHONE]) {
90
+ const svc = new IOSDiscoveryService_1.IOSDiscoveryService(context({ bindHostOrIp: IP }, 4724));
91
+ const proto = IOSDiscoveryService_1.IOSDiscoveryService.prototype;
92
+ Object.assign(svc, {
93
+ fetchLocalIOSDevices: real(proto.fetchLocalIOSDevices),
94
+ fetchLocalSimulators: real(proto.fetchLocalSimulators),
95
+ });
96
+ svc.trackingInitialized = true; // no usbmux listener
97
+ sinon_1.default.stub(svc, 'getConnectedDevices').resolves(connected);
98
+ sinon_1.default.stub(svc, 'fetchRealDeviceNetworkIp').resolves(''); // no go-ios
99
+ return svc;
100
+ }
101
+ beforeEach(() => {
102
+ rows = [];
103
+ sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
104
+ findDevice: (filter) => __awaiter(void 0, void 0, void 0, function* () { var _a; return (_a = rows.find((r) => Object.entries(filter).every(([k, v]) => r[k] === v))) !== null && _a !== void 0 ? _a : null; }),
105
+ });
106
+ const real = typedi_1.Container.get.bind(typedi_1.Container);
107
+ sinon_1.default.stub(typedi_1.Container, 'get').callsFake((token) => {
108
+ if (token === PortAllocator_1.PortAllocator) {
109
+ return { tryAcquire: (purpose) => __awaiter(void 0, void 0, void 0, function* () { return (purpose === 'wda' ? 28400 : 29400); }) };
110
+ }
111
+ if (token === IOSStreamService_1.default)
112
+ return { getStreamStatus: () => undefined };
113
+ return real(token);
114
+ });
115
+ sinon_1.default.stub(deviceUtils, 'getUtilizationTime').resolves(0);
116
+ sinon_1.default.stub(appium_ios_device_1.utilities, 'getOSVersion').resolves('26.7');
117
+ sinon_1.default.stub(appium_ios_device_1.utilities, 'getDeviceName').resolves('iPhone 17 Pro');
118
+ sinon_1.default.stub(appium_ios_device_1.utilities, 'getDeviceInfo').rejects(new Error('no lockdown in tests'));
119
+ });
120
+ afterEach(() => sinon_1.default.restore());
121
+ describe('a real iPhone', () => {
122
+ it("builds the hub's own row when only the node's row for the udid is known", () => __awaiter(void 0, void 0, void 0, function* () {
123
+ rows = [nodeRow(IPHONE)];
124
+ const [phone] = yield hubDiscovery().fetchLocalIOSDevices(rows);
125
+ (0, chai_1.expect)(phone.host).to.equal(HUB);
126
+ (0, chai_1.expect)(phone.busy, "the node's session is not the hub's").to.equal(false);
127
+ (0, chai_1.expect)(phone.session_id).to.not.equal('node-session-1');
128
+ (0, chai_1.expect)(phone.wdaLocalPort, "the hub's own port").to.equal(28400);
129
+ }));
130
+ it("reuses the hub's own row", () => __awaiter(void 0, void 0, void 0, function* () {
131
+ const own = nodeRow(IPHONE, {
132
+ host: HUB,
133
+ wdaLocalPort: 28123,
134
+ busy: false,
135
+ session_id: undefined,
136
+ });
137
+ rows = [nodeRow(IPHONE), own];
138
+ const svc = hubDiscovery();
139
+ const getDeviceInfo = sinon_1.default.spy(svc, 'getDeviceInfo');
140
+ const [phone] = yield svc.fetchLocalIOSDevices(rows);
141
+ (0, chai_1.expect)(getDeviceInfo.called, 'no need to ask the phone again').to.equal(false);
142
+ (0, chai_1.expect)(phone.host).to.equal(HUB);
143
+ (0, chai_1.expect)(phone.wdaLocalPort).to.equal(28123);
144
+ }));
145
+ it("an attach (getDeviceInfo) never takes the node's row", () => __awaiter(void 0, void 0, void 0, function* () {
146
+ rows = [nodeRow(IPHONE)];
147
+ const phone = yield hubDiscovery().getDeviceInfo(IPHONE);
148
+ (0, chai_1.expect)(phone.host).to.equal(HUB);
149
+ (0, chai_1.expect)(phone.wdaLocalPort).to.equal(28400);
150
+ (0, chai_1.expect)(phone.mjpegServerPort).to.equal(29400);
151
+ (0, chai_1.expect)(phone.busy).to.equal(false);
152
+ }));
153
+ });
154
+ it("a simulator on the shared Mac gets the hub's own ports, not the node's row's", () => __awaiter(void 0, void 0, void 0, function* () {
155
+ class FakeSimctl {
156
+ list() {
157
+ return __awaiter(this, void 0, void 0, function* () {
158
+ return { runtimes: [] };
159
+ });
160
+ }
161
+ getDevicesByParsing(platform) {
162
+ return __awaiter(this, void 0, void 0, function* () {
163
+ return platform === 'iOS'
164
+ ? { '26.0': [{ udid: SIM, name: 'iPhone 17', state: 'Booted', sdk: '26.0' }] }
165
+ : {};
166
+ });
167
+ }
168
+ }
169
+ sinon_1.default.stub(simctlModule, 'default').value(FakeSimctl);
170
+ rows = [nodeRow(SIM, { realDevice: false })];
171
+ const [sim] = yield hubDiscovery([]).fetchLocalSimulators();
172
+ (0, chai_1.expect)(sim.host).to.equal(HUB);
173
+ (0, chai_1.expect)(sim.wdaLocalPort).to.equal(28400);
174
+ (0, chai_1.expect)(sim.mjpegServerPort).to.equal(29400);
175
+ }));
176
+ it("IOSDeviceManager reads a phone's stored screen size from that phone's own row", () => __awaiter(void 0, void 0, void 0, function* () {
177
+ rows = [
178
+ nodeRow(IPHONE),
179
+ nodeRow(IPHONE, { host: HUB, screenWidth: '750', screenHeight: '1334' }),
180
+ ];
181
+ // Bypass the constructor: it checks ideviceinstaller.
182
+ const manager = Object.create(IOSDeviceManager_1.default.prototype);
183
+ manager.context = context({ bindHostOrIp: IP }, 4724);
184
+ manager.log = { info: () => undefined, debug: () => undefined, warn: () => undefined };
185
+ const info = yield manager.getAdditionalDeviceInfo({
186
+ udid: IPHONE,
187
+ host: HUB,
188
+ realDevice: true,
189
+ });
190
+ (0, chai_1.expect)(info.screenWidth).to.equal('750');
191
+ (0, chai_1.expect)(info.screenHeight).to.equal('1334');
192
+ }));
193
+ });
@@ -0,0 +1,317 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ require("reflect-metadata");
49
+ const chai_1 = require("chai");
50
+ const child_process_1 = __importDefault(require("child_process"));
51
+ const events_1 = require("events");
52
+ const sinon_1 = __importDefault(require("sinon"));
53
+ const tcp_port_used_1 = __importDefault(require("tcp-port-used"));
54
+ const typedi_1 = require("typedi");
55
+ const util_1 = require("util");
56
+ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
57
+ const singleFlight_1 = require("../../src/helpers/singleFlight");
58
+ const PortAllocator_1 = require("../../src/services/PortAllocator");
59
+ const ProcessRegistry_1 = require("../../src/services/ProcessRegistry");
60
+ const device_store_1 = require("../../src/data-service/device-store");
61
+ const deviceService = __importStar(require("../../src/data-service/device-service"));
62
+ const control_1 = require("../../src/app/routers/control");
63
+ /**
64
+ * go-ios's tunnel process serves its tunnel-info API on 60105 and the first
65
+ * phone's userspace tunnel on 60106. On iOS 17+ a WDA that go-ios launched
66
+ * (`runwda`) reaches the phone through it, and an Appium session rides such a
67
+ * WDA whenever it was allocated while a stream ran: iOSCapabilities points
68
+ * `webDriverAgentUrl` at the stream's WDA.
69
+ *
70
+ * The stream service's orphan sweep kill -9'd whatever listened on those
71
+ * ports, and a viewer's stop killed the stream's own WDA and tunnel, with a
72
+ * live Appium session on the phone either way.
73
+ *
74
+ * Nothing real runs here. Every `exec` goes to a fake `execFile` (exec calls
75
+ * `module.exports.execFile`, promisified or not), `process.kill` is stubbed,
76
+ * and processes are fakes.
77
+ */
78
+ const IPHONE = 'test-iphone-00008150-tunnel';
79
+ const TUNNEL_PID = 424242;
80
+ const AGENT_LISTENER_PID = '515151';
81
+ class FakeProcess extends events_1.EventEmitter {
82
+ constructor(pid) {
83
+ super();
84
+ this.pid = pid;
85
+ this.exitCode = null;
86
+ this.killed = false;
87
+ this.stdout = new events_1.EventEmitter();
88
+ this.stderr = new events_1.EventEmitter();
89
+ }
90
+ kill() {
91
+ this.killed = true;
92
+ if (this.exitCode === null)
93
+ this.exitCode = 137;
94
+ return true;
95
+ }
96
+ }
97
+ function iosService() {
98
+ // Bypass the constructor: it starts watchdog intervals.
99
+ const svc = Object.create(IOSStreamService_1.default.prototype);
100
+ svc.sessions = new Map();
101
+ svc.startFlight = new singleFlight_1.SingleFlight();
102
+ svc.recoveryCooldowns = new Map();
103
+ svc.RECOVERY_COOLDOWN_MS = 30000;
104
+ svc.STREAM_PORT_TTL_MS = 90 * 60 * 1000;
105
+ svc.goIOSPath = '/nonexistent/go-ios';
106
+ return svc;
107
+ }
108
+ describe('go-ios tunnels under a live Appium session (fake exec, fake processes)', () => {
109
+ let device;
110
+ let findDevice;
111
+ let commands;
112
+ let kill;
113
+ let allocator;
114
+ let svc;
115
+ const killed = () => commands.filter((c) => /^kill\b|^pkill\b/.test(c));
116
+ const swept = () => commands.filter((c) => /^lsof\b|^pgrep\b/.test(c));
117
+ /** A stream that launched its own WDA, forwarders and go-ios tunnel. */
118
+ function ownStream() {
119
+ const s = {
120
+ udid: IPHONE,
121
+ wdaProcess: new FakeProcess(),
122
+ forwardWDAProcess: new FakeProcess(),
123
+ forwardMJPEGProcess: new FakeProcess(),
124
+ tunnelProcess: new FakeProcess(TUNNEL_PID),
125
+ wdaPort: 28101,
126
+ mjpegPort: 29101,
127
+ status: 'running',
128
+ lastViewerAt: Date.now(),
129
+ viewerCount: 0,
130
+ };
131
+ svc.sessions.set(IPHONE, s);
132
+ return s;
133
+ }
134
+ /** A stream attached to the WDA the Appium session forwards: only its MJPEG forwarder is its own. */
135
+ function attachedStream() {
136
+ const s = {
137
+ udid: IPHONE,
138
+ wdaProcess: null,
139
+ forwardWDAProcess: null,
140
+ forwardMJPEGProcess: new FakeProcess(),
141
+ tunnelProcess: null,
142
+ wdaPort: 28123,
143
+ mjpegPort: 29101,
144
+ status: 'running',
145
+ lastViewerAt: Date.now(),
146
+ viewerCount: 0,
147
+ };
148
+ svc.sessions.set(IPHONE, s);
149
+ return s;
150
+ }
151
+ beforeEach(() => {
152
+ device = {
153
+ udid: IPHONE,
154
+ host: 'http://127.0.0.1:4723',
155
+ platform: 'ios',
156
+ busy: true,
157
+ session_id: '7f0c5a52-appium-session',
158
+ wdaLocalPort: 28123,
159
+ };
160
+ commands = [];
161
+ // Refuse everything real. lsof names the go-ios agent's listener, pgrep a
162
+ // tunnel for this udid; both exit 1 with nothing to report otherwise.
163
+ sinon_1.default.stub(child_process_1.default, 'execFile').callsFake(((cmd, _opts, cb) => {
164
+ commands.push(cmd);
165
+ const stdout = /^lsof -ti :6010[56]$/.test(cmd) ? `${AGENT_LISTENER_PID}\n` : '';
166
+ const nothingFound = /^(lsof|pgrep)\b/.test(cmd) && !stdout;
167
+ const done = typeof cb === 'function' ? cb : () => undefined;
168
+ process.nextTick(() => nothingFound
169
+ ? done(Object.assign(new Error('exit 1'), { code: 1 }), '', '')
170
+ : done(null, stdout, ''));
171
+ return new events_1.EventEmitter();
172
+ }));
173
+ kill = sinon_1.default.stub(process, 'kill');
174
+ });
175
+ // Prove the fake takes exec before anything could run a real kill.
176
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
177
+ yield (0, util_1.promisify)(child_process_1.default.exec)('echo xenon-exec-guard');
178
+ (0, chai_1.expect)(commands, 'exec must reach the fake').to.deep.equal(['echo xenon-exec-guard']);
179
+ commands.length = 0;
180
+ }));
181
+ beforeEach(() => {
182
+ allocator = {
183
+ claim: sinon_1.default.stub().resolves(true),
184
+ acquire: sinon_1.default.stub().resolves(29102),
185
+ release: sinon_1.default.stub().resolves(),
186
+ };
187
+ svc = iosService();
188
+ const real = typedi_1.Container.get.bind(typedi_1.Container);
189
+ sinon_1.default.stub(typedi_1.Container, 'get').callsFake((token) => {
190
+ if (token === IOSStreamService_1.default)
191
+ return svc;
192
+ if (token === PortAllocator_1.PortAllocator)
193
+ return allocator;
194
+ if (token === ProcessRegistry_1.ProcessRegistry)
195
+ return { track: () => undefined };
196
+ return real(token);
197
+ });
198
+ findDevice = sinon_1.default.stub().callsFake(() => __awaiter(void 0, void 0, void 0, function* () { return device; }));
199
+ sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
200
+ findDevice,
201
+ updateDevice: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }),
202
+ });
203
+ sinon_1.default.stub(deviceService, 'unblockDevice').resolves();
204
+ });
205
+ afterEach(() => sinon_1.default.restore());
206
+ describe("a viewer's stop (stream/stop, stream/leave)", () => {
207
+ it("leaves a stream's own WDA and go-ios tunnel running while an Appium session holds the phone", () => __awaiter(void 0, void 0, void 0, function* () {
208
+ const stream = ownStream();
209
+ yield control_1.previewLeaveDeps.stop(IPHONE);
210
+ (0, chai_1.expect)(stream.wdaProcess.killed, 'the WDA the session may be driving').to.equal(false);
211
+ (0, chai_1.expect)(stream.forwardWDAProcess.killed, "that WDA's forwarder").to.equal(false);
212
+ (0, chai_1.expect)(kill.called, 'no process group is signalled (the tunnel)').to.equal(false);
213
+ (0, chai_1.expect)(killed(), 'no kill is run').to.deep.equal([]);
214
+ (0, chai_1.expect)(svc.getStreamStatus(IPHONE), 'the stream stays, for the session teardown').to.equal(stream);
215
+ (0, chai_1.expect)(allocator.release.called, 'its ports stay leased').to.equal(false);
216
+ }));
217
+ it('sweeps no go-ios port when the stream is attached to the Appium session’s own WDA', () => __awaiter(void 0, void 0, void 0, function* () {
218
+ const stream = attachedStream();
219
+ yield control_1.previewLeaveDeps.stop(IPHONE);
220
+ (0, chai_1.expect)(swept(), 'no lsof/pgrep for go-ios processes').to.deep.equal([]);
221
+ (0, chai_1.expect)(killed(), 'no kill -9 of the go-ios listener').to.deep.equal([]);
222
+ (0, chai_1.expect)(kill.called).to.equal(false);
223
+ (0, chai_1.expect)(stream.forwardMJPEGProcess.killed, 'its own MJPEG forwarder still goes').to.equal(true);
224
+ (0, chai_1.expect)(svc.getStreamStatus(IPHONE)).to.equal(undefined);
225
+ }));
226
+ it('still stops everything and sweeps real orphans once no Appium session holds the phone', () => __awaiter(void 0, void 0, void 0, function* () {
227
+ Object.assign(device, { busy: false, session_id: null });
228
+ const stream = ownStream();
229
+ yield control_1.previewLeaveDeps.stop(IPHONE);
230
+ (0, chai_1.expect)(stream.wdaProcess.killed).to.equal(true);
231
+ (0, chai_1.expect)(stream.forwardWDAProcess.killed).to.equal(true);
232
+ (0, chai_1.expect)(stream.forwardMJPEGProcess.killed).to.equal(true);
233
+ (0, chai_1.expect)(kill.calledWith(-TUNNEL_PID, 'SIGKILL'), "the stream's tunnel group").to.equal(true);
234
+ (0, chai_1.expect)(commands).to.include(`pgrep -f "ios tunnel.*${IPHONE}"`);
235
+ (0, chai_1.expect)(commands).to.include('lsof -ti :60105');
236
+ (0, chai_1.expect)(commands).to.include('lsof -ti :60106');
237
+ (0, chai_1.expect)(killed()).to.include(`kill -9 ${AGENT_LISTENER_PID}`);
238
+ (0, chai_1.expect)(svc.getStreamStatus(IPHONE)).to.equal(undefined);
239
+ }));
240
+ it('treats a live-preview hold as no Appium session', () => __awaiter(void 0, void 0, void 0, function* () {
241
+ Object.assign(device, { session_id: `manual_usr_alice_${IPHONE}` });
242
+ const stream = ownStream();
243
+ yield control_1.previewLeaveDeps.stop(IPHONE);
244
+ (0, chai_1.expect)(stream.wdaProcess.killed).to.equal(true);
245
+ (0, chai_1.expect)(killed()).to.include(`kill -9 ${AGENT_LISTENER_PID}`);
246
+ }));
247
+ });
248
+ describe('a start that attaches to the WDA the Appium session forwards', () => {
249
+ it('stops the stale stream without touching the go-ios tunnel', () => __awaiter(void 0, void 0, void 0, function* () {
250
+ // A start that failed earlier: its session stays in the map, errored.
251
+ svc.sessions.set(IPHONE, {
252
+ udid: IPHONE,
253
+ wdaProcess: null,
254
+ forwardWDAProcess: null,
255
+ forwardMJPEGProcess: null,
256
+ tunnelProcess: null,
257
+ wdaPort: 28101,
258
+ mjpegPort: 29101,
259
+ status: 'error',
260
+ lastViewerAt: Date.now(),
261
+ viewerCount: 0,
262
+ });
263
+ svc.isWDARunning = (port) => __awaiter(void 0, void 0, void 0, function* () { return port === device.wdaLocalPort; });
264
+ svc.updateWDASettings = () => __awaiter(void 0, void 0, void 0, function* () { return undefined; });
265
+ sinon_1.default.stub(tcp_port_used_1.default, 'check').resolves(true);
266
+ const { wdaPort } = yield svc.startStream(IPHONE);
267
+ (0, chai_1.expect)(wdaPort, 'attached to the session’s WDA').to.equal(device.wdaLocalPort);
268
+ (0, chai_1.expect)(swept(), 'no lsof/pgrep for go-ios processes').to.deep.equal([]);
269
+ (0, chai_1.expect)(killed(), 'no kill -9 of the go-ios listener').to.deep.equal([]);
270
+ (0, chai_1.expect)(kill.called).to.equal(false);
271
+ }));
272
+ });
273
+ describe('a start over a running stream whose WDA does not answer', () => {
274
+ beforeEach(() => {
275
+ // WDA doesn't answer /status: busy with a long command, or dead.
276
+ svc.isWDARunning = () => __awaiter(void 0, void 0, void 0, function* () { return false; });
277
+ // A restart that gets past the stop fails right after: no go-ios here.
278
+ svc.isGoIOSAvailable = () => __awaiter(void 0, void 0, void 0, function* () { return false; });
279
+ });
280
+ it('never restarts it while an Appium session holds the phone, and says why', () => __awaiter(void 0, void 0, void 0, function* () {
281
+ var _a;
282
+ const stream = ownStream();
283
+ const err = yield svc.startStream(IPHONE).then(() => null, (e) => e);
284
+ (0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message).to.match(/Appium session .* holds the device/);
285
+ (0, chai_1.expect)(stream.wdaProcess.killed, 'the WDA the session may be driving').to.equal(false);
286
+ (0, chai_1.expect)(stream.forwardWDAProcess.killed).to.equal(false);
287
+ (0, chai_1.expect)(kill.called, 'no process group is signalled (the tunnel)').to.equal(false);
288
+ (0, chai_1.expect)(commands, 'nothing is exec’d').to.deep.equal([]);
289
+ (0, chai_1.expect)((_a = svc.getStreamStatus(IPHONE)) === null || _a === void 0 ? void 0 : _a.status, 'the stream is left as it was').to.equal('running');
290
+ (0, chai_1.expect)(svc.recoveryCooldowns.has(IPHONE), 'no failed start to cool down from').to.equal(false);
291
+ }));
292
+ it("never restarts one attached to the session's own WDA either", () => __awaiter(void 0, void 0, void 0, function* () {
293
+ // A restart would launch a second WebDriverAgent over the session's.
294
+ const stream = attachedStream();
295
+ const err = yield svc.startStream(IPHONE).then(() => null, (e) => e);
296
+ (0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message).to.match(/Appium session .* holds the device/);
297
+ (0, chai_1.expect)(stream.forwardMJPEGProcess.killed).to.equal(false);
298
+ (0, chai_1.expect)(svc.getStreamStatus(IPHONE)).to.equal(stream);
299
+ }));
300
+ it('still restarts it when no session holds the phone, to recover a dead WDA', () => __awaiter(void 0, void 0, void 0, function* () {
301
+ Object.assign(device, { busy: false, session_id: null });
302
+ const stream = ownStream();
303
+ const err = yield svc.startStream(IPHONE).then(() => null, (e) => e);
304
+ (0, chai_1.expect)(stream.wdaProcess.killed, 'the dead stream is stopped').to.equal(true);
305
+ (0, chai_1.expect)(kill.calledWith(-TUNNEL_PID, 'SIGKILL')).to.equal(true);
306
+ (0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message, 'and a new start is attempted').to.equal('go-ios not available');
307
+ }));
308
+ });
309
+ it("leaves go-ios processes alone when the phone's row can't be read", () => __awaiter(void 0, void 0, void 0, function* () {
310
+ attachedStream();
311
+ findDevice.rejects(new Error('database is locked'));
312
+ yield svc.stopStream(IPHONE);
313
+ (0, chai_1.expect)(swept()).to.deep.equal([]);
314
+ (0, chai_1.expect)(killed()).to.deep.equal([]);
315
+ (0, chai_1.expect)(kill.called).to.equal(false);
316
+ }));
317
+ });