@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
@@ -49,7 +49,7 @@ require("reflect-metadata");
49
49
  const chai_1 = require("chai");
50
50
  const sinon_1 = __importDefault(require("sinon"));
51
51
  const express_1 = __importDefault(require("express"));
52
- const supertest_1 = __importDefault(require("supertest"));
52
+ const loopbackRequest_1 = __importDefault(require("../../helpers/loopbackRequest"));
53
53
  const sdk_leases_1 = require("../../../src/app/routers/sdk-leases");
54
54
  const LeaseService_1 = require("../../../src/services/lease/LeaseService");
55
55
  const leaseToken = __importStar(require("../../../src/services/lease/leaseToken"));
@@ -107,7 +107,10 @@ describe('lease token routes reveal nothing without the token', () => {
107
107
  },
108
108
  portLease: { deleteMany: sinon_1.default.stub().resolves({ count: 0 }) },
109
109
  };
110
- store = { updateDevice: sinon_1.default.stub().resolves() };
110
+ store = {
111
+ updateDevice: sinon_1.default.stub().resolves(),
112
+ releaseLeaseLock: sinon_1.default.stub().resolves(true),
113
+ };
111
114
  const svc = new LeaseService_1.LeaseService(db, store, {}, { nodePairAuth: () => __awaiter(void 0, void 0, void 0, function* () { return ({ accessKey: '', token: '' }); }) });
112
115
  const app = (0, express_1.default)();
113
116
  app.use(express_1.default.json());
@@ -136,8 +139,8 @@ describe('lease token routes reveal nothing without the token', () => {
136
139
  function call(op, id, token) {
137
140
  const url = `/xenon/api/sdk/leases/${id}`;
138
141
  if (op === 'release')
139
- return (0, supertest_1.default)(server).delete(url).set('x-xenon-lease-token', token);
140
- const r = (0, supertest_1.default)(server).post(`${url}/${op}`).set('x-xenon-lease-token', token);
142
+ return (0, loopbackRequest_1.default)(server).delete(url).set('x-xenon-lease-token', token);
143
+ const r = (0, loopbackRequest_1.default)(server).post(`${url}/${op}`).set('x-xenon-lease-token', token);
141
144
  return op === 'extend' ? r.send({ durationMs: 60000 }) : r;
142
145
  }
143
146
  const answer = (r) => ({
@@ -177,6 +180,7 @@ describe('lease token routes reveal nothing without the token', () => {
177
180
  (0, chai_1.expect)(db.lease.updateMany.called).to.equal(false);
178
181
  (0, chai_1.expect)(db.portLease.deleteMany.called).to.equal(false);
179
182
  (0, chai_1.expect)(store.updateDevice.called).to.equal(false);
183
+ (0, chai_1.expect)(store.releaseLeaseLock.called).to.equal(false);
180
184
  }));
181
185
  it(`the right token on an expired lease gets ${GONE[op]}`, () => __awaiter(void 0, void 0, void 0, function* () {
182
186
  (0, chai_1.expect)((yield call(op, 'lse_expired', TOKEN)).status).to.equal(GONE[op]);
@@ -189,9 +193,18 @@ describe('lease token routes reveal nothing without the token', () => {
189
193
  }));
190
194
  });
191
195
  }
192
- it('heartbeat: the right token on a lease past its expiry, not yet swept, gets 410', () => __awaiter(void 0, void 0, void 0, function* () {
193
- (0, chai_1.expect)((yield call('heartbeat', 'lse_lapsed', TOKEN)).status).to.equal(410);
194
- }));
196
+ // Neither may keep a lapsed lease alive: extend used to push its expiresAt
197
+ // out again and reset its heartbeat clock, so the sweeper never took it.
198
+ for (const op of ['heartbeat', 'extend']) {
199
+ it(`${op}: the right token on a lease past its expiry, not yet swept, gets 410 and changes nothing`, () => __awaiter(void 0, void 0, void 0, function* () {
200
+ const r = yield call(op, 'lse_lapsed', TOKEN);
201
+ (0, chai_1.expect)({ status: r.status, error: r.body.error }).to.deep.equal({
202
+ status: 410,
203
+ error: 'gone',
204
+ });
205
+ (0, chai_1.expect)(db.lease.updateMany.called).to.equal(false);
206
+ }));
207
+ }
195
208
  it('an unknown id still costs one token comparison, against one fixed hash', () => __awaiter(void 0, void 0, void 0, function* () {
196
209
  const verify = sinon_1.default.spy(leaseToken, 'verifyToken');
197
210
  yield call('heartbeat', 'lse_nope', WRONG);
@@ -65,6 +65,7 @@ const logger_1 = require("../../../src/logger");
65
65
  const RequestLogService_1 = require("../../../src/services/RequestLogService");
66
66
  const EventBus_1 = require("../../../src/services/EventBus");
67
67
  const JwtKeyService_1 = require("../../../src/services/token/JwtKeyService");
68
+ const hubSessionToken_1 = require("../../../src/gateway/hubSessionToken");
68
69
  const prisma_1 = require("../../../src/prisma");
69
70
  const container_registration_1 = require("../../helpers/container-registration");
70
71
  // createSession end to end, from the capabilities a client sends to the
@@ -184,7 +185,7 @@ describe('createSession — a lease-bound session proves it holds the lease', ()
184
185
  }));
185
186
  sinon_1.default.stub(pendingSessions, 'removePendingSession').resolves();
186
187
  sinon_1.default.stub(deviceService, 'updateDeviceProgress').resolves();
187
- sinon_1.default.stub(deviceService, 'updatedAllocatedDevice').resolves();
188
+ sinon_1.default.stub(deviceService, 'claimDeviceForSession').resolves(true);
188
189
  svc = new SessionLifecycleService_1.SessionLifecycleService();
189
190
  // Everything past finalizeSession's own bookkeeping is out of scope here;
190
191
  // what matters is the session response that becomes the Session row.
@@ -343,11 +344,15 @@ describe('createSession — a lease-bound session proves it holds the lease', ()
343
344
  forwardedCaps = JSON.parse(JSON.stringify(c));
344
345
  return { protocol: 'W3C', value: ['sess-1', {}, 'W3C'] };
345
346
  }));
347
+ // The hub's token needs its signing key, which this spec has no use for.
348
+ sinon_1.default.stub(hubSessionToken_1.HubSessionTokenIssuer.prototype, 'createTokenFor').resolves('HUB-TOKEN');
346
349
  });
347
- it('forwards the token to a peer Xenon node, which runs the same check', () => __awaiter(void 0, void 0, void 0, function* () {
350
+ it('forwards neither the lease id nor its token to a peer Xenon node: the hub resolved the lease to its phone', () => __awaiter(void 0, void 0, void 0, function* () {
348
351
  device.nodeId = 'node-peer';
349
352
  yield create(sessionCaps({ leaseToken: TOKEN }));
350
- (0, chai_1.expect)(forwardedCaps.alwaysMatch['xe:options'].leaseToken).to.equal(TOKEN);
353
+ (0, chai_1.expect)(JSON.stringify(forwardedCaps)).to.not.include(TOKEN);
354
+ (0, chai_1.expect)(JSON.stringify(forwardedCaps)).to.not.include('lse_1');
355
+ (0, chai_1.expect)(forwardedCaps.alwaysMatch['appium:udid']).to.equal('u1');
351
356
  (0, chai_1.expect)(JSON.stringify(finalCaps)).to.not.include(TOKEN);
352
357
  (0, chai_1.expect)(JSON.stringify(pendingCopy)).to.not.include(TOKEN);
353
358
  }));
@@ -92,7 +92,10 @@ function harness(over) {
92
92
  if (over.maxBufferedBytes !== undefined)
93
93
  deps.maxBufferedBytes = over.maxBufferedBytes;
94
94
  (0, logcatWs_1.attachLogcatWs)(server, deps);
95
- yield new Promise((r) => server.listen(0, r));
95
+ // 127.0.0.1, where the client connects. A listener on every address can
96
+ // share its port with another process's 127.0.0.1 listener, which then
97
+ // answers the handshake instead ("Unexpected server response: 501").
98
+ yield new Promise((r) => server.listen(0, '127.0.0.1', r));
96
99
  const port = server.address().port;
97
100
  return {
98
101
  port,
@@ -416,6 +419,14 @@ describe('makeTicketActorAuthorizer', () => {
416
419
  });
417
420
  return { fn, calls };
418
421
  };
422
+ it('leaves a node’s phone busy only by its node’s report to the node', () => __awaiter(void 0, void 0, void 0, function* () {
423
+ // The hub relays this socket from the node, whose own check knows the
424
+ // holder (deviceAccessGuard's spec says why).
425
+ const { fn } = authorizer({ busy: true, nodeBusy: true, session_id: null }, ALICE);
426
+ (0, chai_1.expect)(yield fn(UDID, { actorId: BOB })).to.equal(true);
427
+ const routed = authorizer({ busy: true, nodeBusy: true, session_id: 'appium-1', claimSessionId: 'appium-1' }, ALICE);
428
+ (0, chai_1.expect)(yield routed.fn(UDID, { actorId: BOB })).to.equal(false);
429
+ }));
419
430
  it('denies a device the store cannot resolve', () => __awaiter(void 0, void 0, void 0, function* () {
420
431
  // Fail-closed, and deliberately unlike deviceAccessGuard's fall-through:
421
432
  // there is no 404 behind this call, and getAdbForDevice happily runs
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const chai_1 = require("chai");
16
+ const express_1 = __importDefault(require("express"));
17
+ const http_1 = __importDefault(require("http"));
18
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
19
+ // The drop-in for supertest's request(app) that listens on 127.0.0.1, where
20
+ // it connects, instead of on every address. See test/helpers/loopbackRequest.ts.
21
+ describe('loopbackRequest', () => {
22
+ function echoApp() {
23
+ const app = (0, express_1.default)();
24
+ app.use(express_1.default.json());
25
+ app.all('/echo', (req, res) => {
26
+ res.json({ method: req.method, query: req.query, body: req.body });
27
+ });
28
+ return app;
29
+ }
30
+ it('serves the app on 127.0.0.1, not on every address', () => __awaiter(void 0, void 0, void 0, function* () {
31
+ const server = http_1.default.createServer(echoApp());
32
+ let bound;
33
+ server.on('listening', () => {
34
+ bound = server.address();
35
+ });
36
+ const res = yield (0, loopbackRequest_1.default)(server).get('/echo');
37
+ (0, chai_1.expect)(res.status).to.equal(200);
38
+ (0, chai_1.expect)(bound === null || bound === void 0 ? void 0 : bound.address).to.equal('127.0.0.1');
39
+ }));
40
+ it('closes the server it started once the request is answered', () => __awaiter(void 0, void 0, void 0, function* () {
41
+ const server = http_1.default.createServer(echoApp());
42
+ yield (0, loopbackRequest_1.default)(server).get('/echo');
43
+ (0, chai_1.expect)(server.listening).to.equal(false);
44
+ }));
45
+ it('keeps what supertest does with a request: method, query, body', () => __awaiter(void 0, void 0, void 0, function* () {
46
+ const res = yield (0, loopbackRequest_1.default)(echoApp()).post('/echo').query({ a: '1' }).send({ b: 2 });
47
+ (0, chai_1.expect)(res.body).to.deep.equal({ method: 'POST', query: { a: '1' }, body: { b: 2 } });
48
+ }));
49
+ it('answers several requests sent to one server at once', () => __awaiter(void 0, void 0, void 0, function* () {
50
+ const server = http_1.default.createServer(echoApp());
51
+ const answers = yield Promise.all([
52
+ (0, loopbackRequest_1.default)(server).get('/echo').query({ n: '1' }),
53
+ (0, loopbackRequest_1.default)(server).get('/echo').query({ n: '2' }),
54
+ ]);
55
+ (0, chai_1.expect)(answers.map((r) => r.body.query.n)).to.deep.equal(['1', '2']);
56
+ }));
57
+ it('uses a server that is already listening, and leaves it open', () => __awaiter(void 0, void 0, void 0, function* () {
58
+ const server = http_1.default.createServer(echoApp());
59
+ yield new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
60
+ try {
61
+ const res = yield (0, loopbackRequest_1.default)(server).get('/echo');
62
+ (0, chai_1.expect)(res.status).to.equal(200);
63
+ (0, chai_1.expect)(server.listening).to.equal(true);
64
+ }
65
+ finally {
66
+ yield new Promise((resolve) => server.close(resolve));
67
+ }
68
+ }));
69
+ it('sends to a URL as given', () => __awaiter(void 0, void 0, void 0, function* () {
70
+ const server = http_1.default.createServer(echoApp());
71
+ yield new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
72
+ try {
73
+ const { port } = server.address();
74
+ const res = yield (0, loopbackRequest_1.default)(`http://127.0.0.1:${port}`).get('/echo');
75
+ (0, chai_1.expect)(res.body.method).to.equal('GET');
76
+ }
77
+ finally {
78
+ yield new Promise((resolve) => server.close(resolve));
79
+ }
80
+ }));
81
+ });
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ require("reflect-metadata");
13
+ const chai_1 = require("chai");
14
+ const db_1 = require("../../src/data-service/db");
15
+ const device_store_1 = require("../../src/data-service/device-store");
16
+ const loki_stores_1 = require("../helpers/loki-stores");
17
+ /**
18
+ * markNodeBusy on the in-memory store: the hub's note that a node has just
19
+ * taken its phone for a preview. The Prisma store is covered with the hub's
20
+ * allocation in hub-node-race.spec.ts.
21
+ */
22
+ describe('markNodeBusy (in-memory store)', () => {
23
+ (0, loki_stores_1.useLokiStores)();
24
+ const NODE = 'http://10.0.0.9:4725';
25
+ const phone = (host) => ({
26
+ udid: 'mark-node-busy-s9',
27
+ host,
28
+ platform: 'android',
29
+ busy: false,
30
+ nodeBusy: false,
31
+ offline: false,
32
+ userBlocked: false,
33
+ });
34
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
35
+ (yield db_1.XenonDatabase.DeviceModel).findAndRemove({ udid: 'mark-node-busy-s9' });
36
+ }));
37
+ it('sets nodeBusy and busy on that phone of that node only', () => __awaiter(void 0, void 0, void 0, function* () {
38
+ const model = yield db_1.XenonDatabase.DeviceModel;
39
+ model.insert(phone(NODE));
40
+ model.insert(phone('http://10.0.0.8:4725'));
41
+ yield device_store_1.DeviceStoreFactory.getStore().markNodeBusy('mark-node-busy-s9', NODE);
42
+ const rows = model.find({ udid: 'mark-node-busy-s9' });
43
+ const byHost = Object.fromEntries(rows.map((r) => [r.host, [r.busy, r.nodeBusy]]));
44
+ (0, chai_1.expect)(byHost).to.deep.equal({
45
+ [NODE]: [true, true],
46
+ 'http://10.0.0.8:4725': [false, false],
47
+ });
48
+ }));
49
+ it('does nothing for a phone it doesn’t have', () => __awaiter(void 0, void 0, void 0, function* () {
50
+ yield device_store_1.DeviceStoreFactory.getStore().markNodeBusy('mark-node-busy-s9', NODE);
51
+ (0, chai_1.expect)((yield db_1.XenonDatabase.DeviceModel).find({ udid: 'mark-node-busy-s9' })).to.deep.equal([]);
52
+ }));
53
+ });
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const nodeCreateCaps_1 = require("../../src/services/session/nodeCreateCaps");
5
+ /**
6
+ * The capabilities a hub sends the node whose phone it allocated. The hub has
7
+ * checked the credentials, applied the team rule and resolved the lease; the
8
+ * node gets a request that stands on its own: that one phone, and nothing the
9
+ * node can't act on.
10
+ */
11
+ describe('capsForNode — the copy of a create a node gets', () => {
12
+ const sent = () => ({
13
+ alwaysMatch: {
14
+ platformName: 'Android',
15
+ 'appium:udids': 'phone-1,phone-2',
16
+ 'xe:options': { leaseId: 'lse_1', buildId: 'b-1', token: 'SECRET' },
17
+ },
18
+ firstMatch: [
19
+ { 'appium:udid': 'phone-1', 'appium:systemPort': 8201 },
20
+ { 'appium:udid': 'phone-2', 'xenon:options': { leaseId: 'lse_1', leaseToken: 'SECRET' } },
21
+ ],
22
+ });
23
+ it('names exactly the phone the hub allocated, once, in alwaysMatch', () => {
24
+ const copy = (0, nodeCreateCaps_1.capsForNode)(sent(), 'phone-1');
25
+ (0, chai_1.expect)(copy.alwaysMatch['appium:udid']).to.equal('phone-1');
26
+ (0, chai_1.expect)(copy.alwaysMatch['appium:udids']).to.equal(undefined);
27
+ for (const entry of copy.firstMatch) {
28
+ (0, chai_1.expect)(entry['appium:udid']).to.equal(undefined);
29
+ (0, chai_1.expect)(entry['appium:udids']).to.equal(undefined);
30
+ }
31
+ // What the hub allocated for it travels.
32
+ (0, chai_1.expect)(copy.firstMatch[0]['appium:systemPort']).to.equal(8201);
33
+ });
34
+ it('drops the lease id, which only the hub can resolve, from both namespaces', () => {
35
+ const copy = (0, nodeCreateCaps_1.capsForNode)(sent(), 'phone-1');
36
+ (0, chai_1.expect)(JSON.stringify(copy)).to.not.include('lse_1');
37
+ (0, chai_1.expect)(copy.alwaysMatch['xe:options']).to.deep.equal({ buildId: 'b-1' });
38
+ });
39
+ it('never carries a credential', () => {
40
+ (0, chai_1.expect)(JSON.stringify((0, nodeCreateCaps_1.capsForNode)(sent(), 'phone-1'))).to.not.include('SECRET');
41
+ });
42
+ it('does not modify the caps the hub keeps', () => {
43
+ const caps = sent();
44
+ const before = JSON.stringify(caps);
45
+ (0, nodeCreateCaps_1.capsForNode)(caps, 'phone-1');
46
+ (0, chai_1.expect)(JSON.stringify(caps)).to.equal(before);
47
+ });
48
+ it('copes with missing buckets', () => {
49
+ (0, chai_1.expect)((0, nodeCreateCaps_1.capsForNode)({ alwaysMatch: undefined }, 'p')).to.deep.equal({
50
+ alwaysMatch: { 'appium:udid': 'p' },
51
+ firstMatch: [{}],
52
+ });
53
+ });
54
+ });