@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
@@ -15,7 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  require("reflect-metadata");
16
16
  const chai_1 = require("chai");
17
17
  const express_1 = __importDefault(require("express"));
18
- const supertest_1 = __importDefault(require("supertest"));
18
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
19
19
  const deviceAccessGuard_1 = require("../../src/middleware/deviceAccessGuard");
20
20
  const UDID = 'DEV-1';
21
21
  const ALICE = 'usr_alice';
@@ -66,19 +66,19 @@ function appWith(opts) {
66
66
  }
67
67
  describe('deviceAccessGuard', () => {
68
68
  it('lets the lock owner through', () => __awaiter(void 0, void 0, void 0, function* () {
69
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: ALICE })).post(`/control/${UDID}/tap`);
69
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: ALICE })).post(`/control/${UDID}/tap`);
70
70
  (0, chai_1.expect)(res.status).to.equal(200);
71
71
  (0, chai_1.expect)(res.body.reached).to.equal(true);
72
72
  }));
73
73
  it('denies a foreign holder with 409 and names them', () => __awaiter(void 0, void 0, void 0, function* () {
74
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB })).post(`/control/${UDID}/tap`);
74
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB })).post(`/control/${UDID}/tap`);
75
75
  (0, chai_1.expect)(res.status).to.equal(409);
76
76
  (0, chai_1.expect)(res.body.error).to.equal('device_held_by_another_user');
77
77
  (0, chai_1.expect)(res.body.message).to.contain('alice@example.com');
78
78
  (0, chai_1.expect)(res.body.holder).to.deep.equal({ userId: ALICE, name: 'alice@example.com' });
79
79
  }));
80
80
  it('never blocks a GET', () => __awaiter(void 0, void 0, void 0, function* () {
81
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB })).get(`/control/${UDID}/screenshot`);
81
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB })).get(`/control/${UDID}/screenshot`);
82
82
  (0, chai_1.expect)(res.status).to.equal(200);
83
83
  }));
84
84
  // The clipboard is the one read that is credential-shaped: it routinely holds
@@ -88,26 +88,26 @@ describe('deviceAccessGuard', () => {
88
88
  // reading a stranger's clipboard, so it takes the ownership check despite
89
89
  // being a GET.
90
90
  it('blocks reading the clipboard of a device another user holds', () => __awaiter(void 0, void 0, void 0, function* () {
91
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB })).get(`/control/${UDID}/clipboard`);
91
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB })).get(`/control/${UDID}/clipboard`);
92
92
  (0, chai_1.expect)(res.status).to.equal(409);
93
93
  (0, chai_1.expect)(res.body.error).to.equal('device_held_by_another_user');
94
94
  (0, chai_1.expect)(JSON.stringify(res.body)).to.not.contain('hunter2');
95
95
  }));
96
96
  it('lets the holder read their own clipboard', () => __awaiter(void 0, void 0, void 0, function* () {
97
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: ALICE })).get(`/control/${UDID}/clipboard`);
97
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: ALICE })).get(`/control/${UDID}/clipboard`);
98
98
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
99
99
  (0, chai_1.expect)(res.body.content).to.equal('hunter2');
100
100
  }));
101
101
  it('lets an admin read the clipboard', () => __awaiter(void 0, void 0, void 0, function* () {
102
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, scopes: 'admin' })).get(`/control/${UDID}/clipboard`);
102
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, scopes: 'admin' })).get(`/control/${UDID}/clipboard`);
103
103
  (0, chai_1.expect)(res.status).to.equal(200);
104
104
  }));
105
105
  it('allows the clipboard read when the device is free', () => __awaiter(void 0, void 0, void 0, function* () {
106
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, busy: false })).get(`/control/${UDID}/clipboard`);
106
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, busy: false })).get(`/control/${UDID}/clipboard`);
107
107
  (0, chai_1.expect)(res.status).to.equal(200);
108
108
  }));
109
109
  it('blocks the clipboard of a device running another user’s Appium session', () => __awaiter(void 0, void 0, void 0, function* () {
110
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, sessionId: 'appium-1', sessionOwner: ALICE })).get(`/control/${UDID}/clipboard`);
110
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, sessionId: 'appium-1', sessionOwner: ALICE })).get(`/control/${UDID}/clipboard`);
111
111
  (0, chai_1.expect)(res.status).to.equal(409);
112
112
  (0, chai_1.expect)(res.body.error).to.equal('device_in_use_by_session');
113
113
  }));
@@ -118,28 +118,28 @@ describe('deviceAccessGuard', () => {
118
118
  // made that check decorative, since a reader refused at the socket could GET
119
119
  // the identical bytes here.
120
120
  it('blocks reading the logs of a device another user holds', () => __awaiter(void 0, void 0, void 0, function* () {
121
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB })).get(`/control/${UDID}/logs`);
121
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB })).get(`/control/${UDID}/logs`);
122
122
  (0, chai_1.expect)(res.status).to.equal(409);
123
123
  (0, chai_1.expect)(res.body.error).to.equal('device_held_by_another_user');
124
124
  (0, chai_1.expect)(JSON.stringify(res.body)).to.not.contain('SECRETLOGLINE');
125
125
  }));
126
126
  it('blocks the logs of a device running another user’s Appium session', () => __awaiter(void 0, void 0, void 0, function* () {
127
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, sessionId: 'appium-1', sessionOwner: ALICE })).get(`/control/${UDID}/logs`);
127
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, sessionId: 'appium-1', sessionOwner: ALICE })).get(`/control/${UDID}/logs`);
128
128
  (0, chai_1.expect)(res.status).to.equal(409);
129
129
  (0, chai_1.expect)(res.body.error).to.equal('device_in_use_by_session');
130
130
  (0, chai_1.expect)(JSON.stringify(res.body)).to.not.contain('SECRETLOGLINE');
131
131
  }));
132
132
  it('lets the holder read their own logs', () => __awaiter(void 0, void 0, void 0, function* () {
133
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: ALICE })).get(`/control/${UDID}/logs`);
133
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: ALICE })).get(`/control/${UDID}/logs`);
134
134
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
135
135
  (0, chai_1.expect)(res.body.logs[0]).to.contain('SECRETLOGLINE');
136
136
  }));
137
137
  it('lets an admin read the logs', () => __awaiter(void 0, void 0, void 0, function* () {
138
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, scopes: 'admin' })).get(`/control/${UDID}/logs`);
138
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, scopes: 'admin' })).get(`/control/${UDID}/logs`);
139
139
  (0, chai_1.expect)(res.status).to.equal(200);
140
140
  }));
141
141
  it('allows the logs read when the device is free', () => __awaiter(void 0, void 0, void 0, function* () {
142
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, busy: false })).get(`/control/${UDID}/logs`);
142
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, busy: false })).get(`/control/${UDID}/logs`);
143
143
  (0, chai_1.expect)(res.status).to.equal(200);
144
144
  }));
145
145
  // Deliberately NOT extended to the other reads: screenshot and the MJPEG
@@ -147,39 +147,39 @@ describe('deviceAccessGuard', () => {
147
147
  // decision for this guard was mutations-only. Clipboard and logs are the
148
148
  // documented exceptions, so this pins the boundary.
149
149
  it('does not extend the ownership check to other reads', () => __awaiter(void 0, void 0, void 0, function* () {
150
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB })).get(`/control/${UDID}/screenshot`);
150
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB })).get(`/control/${UDID}/screenshot`);
151
151
  (0, chai_1.expect)(res.status).to.equal(200);
152
152
  }));
153
153
  it('skips stream/start, stream/stop, stream/leave and stream/ticket', () => __awaiter(void 0, void 0, void 0, function* () {
154
154
  const app = appWith({ actorUserId: BOB });
155
155
  for (const p of ['stream/start', 'stream/stop', 'stream/leave', 'stream/ticket']) {
156
- const res = yield (0, supertest_1.default)(app).post(`/control/${UDID}/${p}`);
156
+ const res = yield (0, loopbackRequest_1.default)(app).post(`/control/${UDID}/${p}`);
157
157
  (0, chai_1.expect)(res.status, p).to.equal(200);
158
158
  }
159
159
  }));
160
160
  it('lets an admin-scoped key through', () => __awaiter(void 0, void 0, void 0, function* () {
161
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, scopes: 'admin' })).post(`/control/${UDID}/tap`);
161
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, scopes: 'admin' })).post(`/control/${UDID}/tap`);
162
162
  (0, chai_1.expect)(res.status).to.equal(200);
163
163
  }));
164
164
  it('lets a SUPER_ADMIN through', () => __awaiter(void 0, void 0, void 0, function* () {
165
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, role: 'SUPER_ADMIN' })).post(`/control/${UDID}/tap`);
165
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, role: 'SUPER_ADMIN' })).post(`/control/${UDID}/tap`);
166
166
  (0, chai_1.expect)(res.status).to.equal(200);
167
167
  }));
168
168
  it('does not treat a scope merely containing "admin" as admin', () => __awaiter(void 0, void 0, void 0, function* () {
169
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, scopes: 'nonadmin,devices' })).post(`/control/${UDID}/tap`);
169
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, scopes: 'nonadmin,devices' })).post(`/control/${UDID}/tap`);
170
170
  (0, chai_1.expect)(res.status).to.equal(409);
171
171
  }));
172
172
  it('401s when there is no actor', () => __awaiter(void 0, void 0, void 0, function* () {
173
- const res = yield (0, supertest_1.default)(appWith({})).post(`/control/${UDID}/tap`);
173
+ const res = yield (0, loopbackRequest_1.default)(appWith({})).post(`/control/${UDID}/tap`);
174
174
  (0, chai_1.expect)(res.status).to.equal(401);
175
175
  }));
176
176
  it('denies a foreign Appium session', () => __awaiter(void 0, void 0, void 0, function* () {
177
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB, sessionId: 'appium-1', sessionOwner: ALICE })).post(`/control/${UDID}/tap`);
177
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB, sessionId: 'appium-1', sessionOwner: ALICE })).post(`/control/${UDID}/tap`);
178
178
  (0, chai_1.expect)(res.status).to.equal(409);
179
179
  (0, chai_1.expect)(res.body.error).to.equal('device_in_use_by_session');
180
180
  }));
181
181
  it('allows the owner of the running Appium session', () => __awaiter(void 0, void 0, void 0, function* () {
182
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: ALICE, sessionId: 'appium-1', sessionOwner: ALICE })).post(`/control/${UDID}/tap`);
182
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: ALICE, sessionId: 'appium-1', sessionOwner: ALICE })).post(`/control/${UDID}/tap`);
183
183
  (0, chai_1.expect)(res.status).to.equal(200);
184
184
  }));
185
185
  it('falls through to the handler when the device is unknown', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -198,7 +198,7 @@ describe('deviceAccessGuard', () => {
198
198
  router.use((0, deviceAccessGuard_1.deviceAccessGuard)({ findDevice: () => __awaiter(void 0, void 0, void 0, function* () { return null; }) }));
199
199
  router.post('/:udid/tap', (_req, res) => res.status(404).send('Device not found'));
200
200
  app.use('/control', router);
201
- const res = yield (0, supertest_1.default)(app).post(`/control/${UDID}/tap`);
201
+ const res = yield (0, loopbackRequest_1.default)(app).post(`/control/${UDID}/tap`);
202
202
  (0, chai_1.expect)(res.status).to.equal(404);
203
203
  }));
204
204
  it('responds instead of hanging on a malformed-percent udid', function () {
@@ -209,13 +209,13 @@ describe('deviceAccessGuard', () => {
209
209
  // this test's timeout well under the file default so a regression fails
210
210
  // fast instead of silently eating the full 60s.
211
211
  this.timeout(5000);
212
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: ALICE })).post('/control/100%/tap');
212
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: ALICE })).post('/control/100%/tap');
213
213
  (0, chai_1.expect)(res.status).to.equal(400);
214
214
  (0, chai_1.expect)(res.body.success).to.equal(false);
215
215
  });
216
216
  });
217
217
  it('fails closed with 503 when the device lookup throws, never reaching the handler', () => __awaiter(void 0, void 0, void 0, function* () {
218
- const res = yield (0, supertest_1.default)(appWith({
218
+ const res = yield (0, loopbackRequest_1.default)(appWith({
219
219
  actorUserId: ALICE,
220
220
  findDeviceImpl: () => __awaiter(void 0, void 0, void 0, function* () {
221
221
  throw new Error('store hiccup');
@@ -230,7 +230,7 @@ describe('deviceAccessGuard', () => {
230
230
  (0, chai_1.expect)(res.body.reached).to.equal(undefined);
231
231
  }));
232
232
  it('fails closed with 503 when resolveSessionOwner throws, never reaching the handler', () => __awaiter(void 0, void 0, void 0, function* () {
233
- const res = yield (0, supertest_1.default)(appWith({
233
+ const res = yield (0, loopbackRequest_1.default)(appWith({
234
234
  actorUserId: BOB,
235
235
  sessionId: 'appium-1',
236
236
  resolveSessionOwnerImpl: () => __awaiter(void 0, void 0, void 0, function* () {
@@ -242,7 +242,7 @@ describe('deviceAccessGuard', () => {
242
242
  (0, chai_1.expect)(res.body.reached).to.equal(undefined);
243
243
  }));
244
244
  it('still denies with 409 (no holder name) when describeHolder throws', () => __awaiter(void 0, void 0, void 0, function* () {
245
- const res = yield (0, supertest_1.default)(appWith({
245
+ const res = yield (0, loopbackRequest_1.default)(appWith({
246
246
  actorUserId: BOB,
247
247
  describeHolderImpl: () => __awaiter(void 0, void 0, void 0, function* () {
248
248
  throw new Error('lookup hiccup');
@@ -256,8 +256,46 @@ describe('deviceAccessGuard', () => {
256
256
  (0, chai_1.expect)(res.body.holder.name).to.equal(undefined);
257
257
  }));
258
258
  it('skips a mixed-case allowlisted action like Stream/Start', () => __awaiter(void 0, void 0, void 0, function* () {
259
- const res = yield (0, supertest_1.default)(appWith({ actorUserId: BOB })).post(`/control/${UDID}/Stream/Start`);
259
+ const res = yield (0, loopbackRequest_1.default)(appWith({ actorUserId: BOB })).post(`/control/${UDID}/Stream/Start`);
260
260
  (0, chai_1.expect)(res.status).to.equal(200);
261
261
  (0, chai_1.expect)(res.body.reached).to.equal(true);
262
262
  }));
263
263
  });
264
+ /**
265
+ * On a hub, a node's phone is busy when its node reports it busy (nodeBusy):
266
+ * a preview the node holds, say. The hub has no holder on record for that:
267
+ * no session and no hold of its own. Failing closed on "busy, owner unknown"
268
+ * refused every non-admin, the preview's own holder included, so the hub
269
+ * leaves that decision to the node, whose own guard knows the holder and
270
+ * judges every call the hub forwards. What the hub holds itself (a claim, a
271
+ * session, a hold) it still judges.
272
+ */
273
+ describe('deviceAccessGuard on a node’s phone', () => {
274
+ const nodePhone = (row) => appWith({ actorUserId: BOB, findDeviceImpl: () => __awaiter(void 0, void 0, void 0, function* () { return row; }), sessionOwner: ALICE });
275
+ it('leaves a phone busy only by its node’s report to the node', () => __awaiter(void 0, void 0, void 0, function* () {
276
+ const app = nodePhone({ busy: true, nodeBusy: true, session_id: null });
277
+ const tap = yield (0, loopbackRequest_1.default)(app).post(`/control/${UDID}/tap`);
278
+ (0, chai_1.expect)(tap.status).to.equal(200);
279
+ (0, chai_1.expect)(tap.body.reached).to.equal(true);
280
+ const logs = yield (0, loopbackRequest_1.default)(app).get(`/control/${UDID}/logs`);
281
+ (0, chai_1.expect)(logs.status).to.equal(200);
282
+ }));
283
+ it('still judges a session this server routed to the node', () => __awaiter(void 0, void 0, void 0, function* () {
284
+ const tap = yield (0, loopbackRequest_1.default)(nodePhone({
285
+ busy: true,
286
+ nodeBusy: true,
287
+ session_id: 'appium-1',
288
+ claimSessionId: 'appium-1',
289
+ })).post(`/control/${UDID}/tap`);
290
+ (0, chai_1.expect)(tap.status).to.equal(409);
291
+ (0, chai_1.expect)(tap.body.error).to.equal('device_in_use_by_session');
292
+ }));
293
+ it('still refuses while this server’s own create for the phone is pending', () => __awaiter(void 0, void 0, void 0, function* () {
294
+ const tap = yield (0, loopbackRequest_1.default)(nodePhone({ busy: true, nodeBusy: true, session_id: null, claimedAt: 1790000000000 })).post(`/control/${UDID}/tap`);
295
+ (0, chai_1.expect)(tap.status).to.equal(409);
296
+ }));
297
+ it('still refuses this server’s own phone busy with no holder on record', () => __awaiter(void 0, void 0, void 0, function* () {
298
+ const tap = yield (0, loopbackRequest_1.default)(nodePhone({ busy: true, session_id: null })).post(`/control/${UDID}/tap`);
299
+ (0, chai_1.expect)(tap.status).to.equal(409);
300
+ }));
301
+ });
@@ -25,7 +25,10 @@ const db_1 = require("../../src/data-service/db");
25
25
  const chai_1 = require("chai");
26
26
  const semver_1 = __importDefault(require("semver"));
27
27
  const test_container_1 = require("../helpers/test-container");
28
+ const loki_stores_1 = require("../helpers/loki-stores");
28
29
  describe('Get device', () => {
30
+ // The in-memory store, also when this file runs on its own.
31
+ (0, loki_stores_1.useLokiStores)();
29
32
  before('Set devices in memory', () => __awaiter(void 0, void 0, void 0, function* () {
30
33
  var _a, e_1, _b, _c;
31
34
  yield (0, test_container_1.resetTestContainer)();
@@ -146,6 +149,7 @@ describe('Get device', () => {
146
149
  finally { if (e_1) throw e_1.error; }
147
150
  }
148
151
  }));
152
+ after(() => (0, test_container_1.resetTestContainer)());
149
153
  it('Get android device based on filter with minSDK', () => __awaiter(void 0, void 0, void 0, function* () {
150
154
  const filterOptions = {
151
155
  platform: 'android',
@@ -0,0 +1,48 @@
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 device_store_1 = require("../../src/data-service/device-store");
15
+ /**
16
+ * clearStorage(onlyHosts) on the in-memory (Loki) store, the one the unit
17
+ * suite runs: a hub starting up forgets its own phones and keeps its nodes'.
18
+ * The Prisma store's side is in session-recovery-routing.spec.ts.
19
+ */
20
+ describe('LokiDeviceStore.clearStorage(onlyHosts)', () => {
21
+ const HUB = 'http://clear-at-boot-hub:4724';
22
+ const NODE = 'http://clear-at-boot-hub:4725';
23
+ let savedStore;
24
+ let store;
25
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
26
+ savedStore = device_store_1.DeviceStoreFactory._deviceStore;
27
+ device_store_1.DeviceStoreFactory._deviceStore = undefined;
28
+ const env = process.env.NODE_ENV;
29
+ process.env.NODE_ENV = 'test';
30
+ store = device_store_1.DeviceStoreFactory.getStore();
31
+ process.env.NODE_ENV = env;
32
+ (0, chai_1.expect)(store.constructor.name).to.equal('LokiDeviceStore');
33
+ yield store.addDevices([
34
+ { udid: 'clear-hub-phone', host: HUB, platform: 'android' },
35
+ { udid: 'clear-node-phone', host: NODE, platform: 'android' },
36
+ ]);
37
+ }));
38
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
39
+ yield store.removeDevices({ udid: 'clear-hub-phone' });
40
+ yield store.removeDevices({ udid: 'clear-node-phone' });
41
+ device_store_1.DeviceStoreFactory._deviceStore = savedStore;
42
+ }));
43
+ it('deletes only the phones filed under the given hosts', () => __awaiter(void 0, void 0, void 0, function* () {
44
+ yield store.clearStorage([HUB]);
45
+ (0, chai_1.expect)(yield store.findDevice({ udid: 'clear-hub-phone' })).to.equal(null);
46
+ (0, chai_1.expect)(yield store.findDevice({ udid: 'clear-node-phone' })).to.include({ host: NODE });
47
+ }));
48
+ });
@@ -21,10 +21,12 @@ const prisma_store_1 = require("../../src/data-service/prisma-store");
21
21
  const device_store_1 = require("../../src/data-service/device-store");
22
22
  const device_service_1 = require("../../src/data-service/device-service");
23
23
  const device_utils_1 = require("../../src/device-utils");
24
+ const localDeviceHosts_1 = require("../../src/device-managers/localDeviceHosts");
24
25
  const device_managers_1 = require("../../src/device-managers");
25
26
  const SocketServer_1 = require("../../src/services/SocketServer");
26
27
  const NotificationService_1 = require("../../src/services/NotificationService");
27
28
  const container_registration_1 = require("../helpers/container-registration");
29
+ const fake_device_table_1 = require("../helpers/fake-device-table");
28
30
  /**
29
31
  * The periodic device sync reads every row, runs discovery (seconds of adb or
30
32
  * simctl), then writes each phone back. A write that lands in between (a
@@ -38,81 +40,7 @@ const container_registration_1 = require("../helpers/container-registration");
38
40
  */
39
41
  const HOST = 'http://127.0.0.1:4723';
40
42
  const BIND = '127.0.0.1';
41
- function fakeDeviceTable(seed) {
42
- const rows = new Map();
43
- const key = (udid, host) => `${udid}|${host}`;
44
- const matches = (row, where) => {
45
- if (!where)
46
- return true;
47
- return Object.entries(where).every(([field, cond]) => {
48
- var _a;
49
- if (field === 'OR')
50
- return cond.some((w) => matches(row, w));
51
- if (field === 'AND')
52
- return cond.every((w) => matches(row, w));
53
- if (cond && typeof cond === 'object' && 'in' in cond)
54
- return cond.in.includes(row[field]);
55
- if (cond && typeof cond === 'object' && 'contains' in cond)
56
- return String((_a = row[field]) !== null && _a !== void 0 ? _a : '').includes(cond.contains);
57
- return row[field] === cond;
58
- });
59
- };
60
- const write = (row, data) => {
61
- for (const [field, value] of Object.entries(data))
62
- if (value !== undefined)
63
- row[field] = value;
64
- };
65
- const defaults = () => ({
66
- busy: false,
67
- userBlocked: false,
68
- offline: false,
69
- session_id: null,
70
- teamId: null,
71
- sessionProgress: '',
72
- sessionStartTime: 0,
73
- totalUtilizationTimeMilliSec: 0,
74
- });
75
- for (const r of seed)
76
- rows.set(key(r.udid, r.host), Object.assign(Object.assign({}, defaults()), r));
77
- return {
78
- row: (udid) => [...rows.values()].find((r) => r.udid === udid),
79
- delegate: {
80
- findMany: (...args_1) => __awaiter(this, [...args_1], void 0, function* (args = {}) { return [...rows.values()].filter((r) => matches(r, args.where)).map((r) => (Object.assign({}, r))); }),
81
- findUnique: (_a) => __awaiter(this, [_a], void 0, function* ({ where }) {
82
- const r = rows.get(key(where.udid_host.udid, where.udid_host.host));
83
- return r ? Object.assign({}, r) : null;
84
- }),
85
- upsert: (_a) => __awaiter(this, [_a], void 0, function* ({ where, create, update, }) {
86
- const k = key(where.udid_host.udid, where.udid_host.host);
87
- const found = rows.get(k);
88
- if (found) {
89
- write(found, update);
90
- return Object.assign({}, found);
91
- }
92
- const created = defaults();
93
- write(created, create);
94
- rows.set(k, created);
95
- return Object.assign({}, created);
96
- }),
97
- updateMany: (_a) => __awaiter(this, [_a], void 0, function* ({ where, data }) {
98
- let count = 0;
99
- for (const r of rows.values()) {
100
- if (matches(r, where)) {
101
- write(r, data);
102
- count++;
103
- }
104
- }
105
- return { count };
106
- }),
107
- deleteMany: (_a) => __awaiter(this, [_a], void 0, function* ({ where }) {
108
- for (const [k, r] of rows)
109
- if (matches(r, where))
110
- rows.delete(k);
111
- return { count: 0 };
112
- }),
113
- },
114
- };
115
- }
43
+ const LOCAL = (0, localDeviceHosts_1.localDeviceHosts)({ bindHostOrIp: BIND }, 4723);
116
44
  /** What AndroidDeviceManager returns for a phone it already knows (~158-166). */
117
45
  const androidEcho = (known) => (Object.assign(Object.assign({}, known), { state: 'device', busy: known.busy || false }));
118
46
  /** What IOSDiscoveryService.getSimulators returns for every simulator, known or not. */
@@ -136,7 +64,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
136
64
  let restoreContainer;
137
65
  let savedStore;
138
66
  const seed = (rows) => {
139
- table = fakeDeviceTable(rows);
67
+ table = (0, fake_device_table_1.fakeDeviceTable)(rows);
140
68
  for (const m of Object.keys(table.delegate)) {
141
69
  sinon_1.default.stub(prisma_1.prisma.device, m).callsFake(table.delegate[m]);
142
70
  }
@@ -178,7 +106,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
178
106
  yield (0, device_service_1.blockDevice)('s9', HOST, 'manual_user-1_s9');
179
107
  return known.map(androidEcho);
180
108
  });
181
- yield (0, device_utils_1.updateDeviceList)(BIND);
109
+ yield (0, device_utils_1.updateDeviceList)(LOCAL);
182
110
  (0, chai_1.expect)(table.row('s9')).to.include({ busy: true, session_id: 'manual_user-1_s9' });
183
111
  }));
184
112
  it('keeps a session lock taken while discovery ran, so the phone is not handed out twice', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -190,7 +118,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
190
118
  (0, chai_1.expect)(locked === null || locked === void 0 ? void 0 : locked.udid).to.equal('s9');
191
119
  return known.map(androidEcho);
192
120
  });
193
- yield (0, device_utils_1.updateDeviceList)(BIND);
121
+ yield (0, device_utils_1.updateDeviceList)(LOCAL);
194
122
  (0, chai_1.expect)(table.row('s9')).to.include({ busy: true });
195
123
  const second = yield device_store_1.DeviceStoreFactory.getStore().findAndLockDevice({
196
124
  platform: 'android',
@@ -203,7 +131,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
203
131
  yield device_store_1.DeviceStoreFactory.getStore().updateDevice('s9', HOST, { teamId: 'team-b' });
204
132
  return known.map(androidEcho);
205
133
  });
206
- yield (0, device_utils_1.updateDeviceList)(BIND);
134
+ yield (0, device_utils_1.updateDeviceList)(LOCAL);
207
135
  (0, chai_1.expect)(table.row('s9')).to.include({ teamId: 'team-b' });
208
136
  }));
209
137
  it("keeps a stream's port written while discovery ran", () => __awaiter(void 0, void 0, void 0, function* () {
@@ -212,7 +140,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
212
140
  yield device_store_1.DeviceStoreFactory.getStore().updateDevice('s9', HOST, { mjpegServerPort: 9200 });
213
141
  return known.map(androidEcho);
214
142
  });
215
- yield (0, device_utils_1.updateDeviceList)(BIND);
143
+ yield (0, device_utils_1.updateDeviceList)(LOCAL);
216
144
  (0, chai_1.expect)(table.row('s9')).to.include({ mjpegServerPort: 9200 });
217
145
  }));
218
146
  it('leaves a simulator in a session busy, though discovery always reports it free', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -232,7 +160,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
232
160
  },
233
161
  ]);
234
162
  discover = () => __awaiter(void 0, void 0, void 0, function* () { return [simulatorAsDiscovered()]; });
235
- yield (0, device_utils_1.updateDeviceList)(BIND);
163
+ yield (0, device_utils_1.updateDeviceList)(LOCAL);
236
164
  (0, chai_1.expect)(table.row('sim-1')).to.include({
237
165
  busy: true,
238
166
  session_id: 'sess-1',
@@ -268,7 +196,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
268
196
  Object.assign(Object.assign({}, known.find((d) => d.udid === 'iphone')), { ip: '10.0.0.9' }),
269
197
  ];
270
198
  });
271
- yield (0, device_utils_1.updateDeviceList)(BIND);
199
+ yield (0, device_utils_1.updateDeviceList)(LOCAL);
272
200
  (0, chai_1.expect)(table.row('sim-1')).to.include({ state: 'Booted', wdaLocalPort: 8101 });
273
201
  (0, chai_1.expect)(table.row('iphone')).to.include({ ip: '10.0.0.9' });
274
202
  }));
@@ -276,7 +204,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
276
204
  seed([S9]);
277
205
  const pixel = Object.assign(Object.assign({}, S9), { udid: 'pixel', name: 'Pixel 8' });
278
206
  discover = (known) => __awaiter(void 0, void 0, void 0, function* () { return [...known.map(androidEcho), pixel]; });
279
- yield (0, device_utils_1.updateDeviceList)(BIND);
207
+ yield (0, device_utils_1.updateDeviceList)(LOCAL);
280
208
  (0, chai_1.expect)(emits.filter(([e]) => e === 'device_added').map(([, d]) => d.udid)).to.deep.equal([
281
209
  'pixel',
282
210
  ]);
@@ -297,16 +225,16 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
297
225
  });
298
226
  (0, chai_1.expect)(emits.filter(([e]) => e === 'device_added')).to.have.length(0);
299
227
  }));
300
- it("the hub stores a node's own report of its phones as sent", () => __awaiter(void 0, void 0, void 0, function* () {
228
+ it("the hub takes a node's report of its phone busy as the node's, not a session of its own", () => __awaiter(void 0, void 0, void 0, function* () {
301
229
  seed([Object.assign(Object.assign({}, S9), { host: 'http://10.0.0.2:4723' })]);
302
- yield (0, device_service_1.addNewDevice)([Object.assign(Object.assign({}, S9), { host: 'http://10.0.0.2:4723', busy: true, session_id: 'node-sess' })], undefined, { mirror: true });
303
- (0, chai_1.expect)(table.row('s9')).to.include({ busy: true, session_id: 'node-sess' });
230
+ yield (0, device_service_1.addNewDevice)([Object.assign(Object.assign({}, S9), { host: 'http://10.0.0.2:4723', busy: true, session_id: 'node-sess' })], undefined, { nodeReport: true });
231
+ (0, chai_1.expect)(table.row('s9')).to.include({ busy: true, nodeBusy: true, session_id: null });
304
232
  (0, chai_1.expect)(emits.filter(([e]) => e === 'device_added')).to.have.length(0);
305
233
  }));
306
234
  });
307
235
  describe('Device sync keeps writes made while it ran, on the Loki test store', () => {
308
236
  const LOKI_HOST = 'http://loki-sync-test:4723';
309
- const LOKI_BIND = 'loki-sync-test';
237
+ const LOKI_LOCAL = (0, localDeviceHosts_1.localDeviceHosts)({ bindHostOrIp: 'loki-sync-test' }, 4723);
310
238
  const UDIDS = ['loki-s9', 'loki-sim', 'loki-pixel'];
311
239
  let emits;
312
240
  let discover;
@@ -358,7 +286,7 @@ describe('Device sync keeps writes made while it ran, on the Loki test store', (
358
286
  yield (0, device_service_1.blockDevice)('loki-s9', LOKI_HOST, 'manual_user-1_loki-s9');
359
287
  return known.filter((d) => d.host === LOKI_HOST).map(androidEcho);
360
288
  });
361
- yield (0, device_utils_1.updateDeviceList)(LOKI_BIND);
289
+ yield (0, device_utils_1.updateDeviceList)(LOKI_LOCAL);
362
290
  (0, chai_1.expect)(yield row('loki-s9')).to.include({ busy: true, session_id: 'manual_user-1_loki-s9' });
363
291
  }));
364
292
  it('leaves a simulator in a session busy, and still takes its new state', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -371,7 +299,7 @@ describe('Device sync keeps writes made while it ran, on the Loki test store', (
371
299
  simulatorAsDiscovered({ udid: 'loki-sim', host: LOKI_HOST, state: 'Booted' }),
372
300
  ];
373
301
  });
374
- yield (0, device_utils_1.updateDeviceList)(LOKI_BIND);
302
+ yield (0, device_utils_1.updateDeviceList)(LOKI_LOCAL);
375
303
  (0, chai_1.expect)(yield row('loki-sim')).to.include({
376
304
  busy: true,
377
305
  session_id: 'sess-1',