@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 typedi_1 = require("typedi");
54
54
  const control_1 = __importStar(require("../../src/app/routers/control"));
55
55
  const device_store_1 = require("../../src/data-service/device-store");
@@ -60,6 +60,9 @@ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/io
60
60
  const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
61
61
  const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
62
62
  const authMiddleware_1 = require("../../src/middleware/authMiddleware");
63
+ const container_registration_1 = require("../helpers/container-registration");
64
+ const loopbackServer_1 = require("../helpers/loopbackServer");
65
+ const own_node_id_1 = require("../helpers/own-node-id");
63
66
  const UDID = 'DEV-1';
64
67
  const ALICE = 'usr_alice';
65
68
  const BOB = 'usr_bob';
@@ -82,6 +85,8 @@ function buildApp(userId) {
82
85
  return app;
83
86
  }
84
87
  describe('POST /control/:udid/stream/leave', () => {
88
+ // The fake rows are this server's own phones.
89
+ (0, own_node_id_1.useOwnNodeId)();
85
90
  let deviceRow;
86
91
  let unblock;
87
92
  let androidStop;
@@ -90,10 +95,16 @@ describe('POST /control/:udid/stream/leave', () => {
90
95
  let h264Clients;
91
96
  let mjpegViewers;
92
97
  let iosViewers;
98
+ let restoreContainer;
99
+ // Served on 127.0.0.1: request(app) listens on every address and connects
100
+ // to 127.0.0.1, where another process's listener on that port can answer.
101
+ const loopback = (0, loopbackServer_1.loopbackServers)();
93
102
  beforeEach(() => {
103
+ restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, AndroidStreamService_1.default, AndroidH264StreamService_1.default, IOSStreamService_1.default, recording_store_1.RecordingStore);
94
104
  deviceRow = {
95
105
  udid: UDID,
96
106
  host: '127.0.0.1',
107
+ nodeId: own_node_id_1.OWN_NODE_ID,
97
108
  platform: 'android',
98
109
  busy: true,
99
110
  session_id: `manual_${ALICE}_${UDID}`,
@@ -126,14 +137,15 @@ describe('POST /control/:udid/stream/leave', () => {
126
137
  isRecording: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
127
138
  });
128
139
  });
129
- afterEach(() => {
140
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
130
141
  sinon_1.default.restore();
131
- typedi_1.Container.reset();
132
- });
142
+ restoreContainer();
143
+ yield loopback.closeAll();
144
+ }));
133
145
  describe('the route', () => {
134
146
  it('schedules the leave for the holder and answers at once', () => __awaiter(void 0, void 0, void 0, function* () {
135
147
  const leave = sinon_1.default.stub(control_1.previewLeaves, 'leave');
136
- const res = yield (0, supertest_1.default)(buildApp(ALICE)).post(`/xenon/api/control/${UDID}/stream/leave`);
148
+ const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(ALICE))).post(`/xenon/api/control/${UDID}/stream/leave`);
137
149
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(202);
138
150
  (0, chai_1.expect)(leave.calledOnceWith(UDID)).to.equal(true);
139
151
  // Nothing is stopped yet: that waits for the grace and the viewer count.
@@ -141,14 +153,14 @@ describe('POST /control/:udid/stream/leave', () => {
141
153
  }));
142
154
  it('refuses someone else’s hold', () => __awaiter(void 0, void 0, void 0, function* () {
143
155
  const leave = sinon_1.default.stub(control_1.previewLeaves, 'leave');
144
- const res = yield (0, supertest_1.default)(buildApp(BOB)).post(`/xenon/api/control/${UDID}/stream/leave`);
156
+ const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(BOB))).post(`/xenon/api/control/${UDID}/stream/leave`);
145
157
  (0, chai_1.expect)(res.status).to.equal(403);
146
158
  (0, chai_1.expect)(leave.called).to.equal(false);
147
159
  }));
148
160
  // A reload leaves and then starts again; the start must win.
149
161
  it('stream/start cancels a pending leave', () => __awaiter(void 0, void 0, void 0, function* () {
150
162
  const cancel = sinon_1.default.stub(control_1.previewLeaves, 'cancel');
151
- yield (0, supertest_1.default)(buildApp(ALICE)).post(`/xenon/api/control/${UDID}/stream/start`);
163
+ yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(ALICE))).post(`/xenon/api/control/${UDID}/stream/start`);
152
164
  (0, chai_1.expect)(cancel.calledWith(UDID)).to.equal(true);
153
165
  }));
154
166
  });
@@ -0,0 +1,164 @@
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 IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
53
+ const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
54
+ const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
55
+ const previewHold_1 = require("../../src/device-managers/android/previewHold");
56
+ const singleFlight_1 = require("../../src/helpers/singleFlight");
57
+ const device_store_1 = require("../../src/data-service/device-store");
58
+ const deviceService = __importStar(require("../../src/data-service/device-service"));
59
+ const PluginContext_1 = require("../../src/PluginContext");
60
+ const PortAllocator_1 = require("../../src/services/PortAllocator");
61
+ const recording_store_1 = require("../../src/services/recording/recording-store");
62
+ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
63
+ /**
64
+ * A hub's table holds its nodes' rows too, and a node on the same machine
65
+ * sees the same phones (an iPhone, emulator-5554). The stream services
66
+ * looked a phone up by udid alone, so on such a hub they could act on the
67
+ * node's row: release the node's preview hold, or judge "an Appium session
68
+ * holds this phone" from the node's session. A phone's own row is the one
69
+ * filed under a host this server's discovery writes (localDeviceHosts).
70
+ *
71
+ * The store fake matches every key of the filter, as Prisma's findFirst does.
72
+ */
73
+ const IP = '192.168.0.104';
74
+ const HUB = `http://${IP}:4724`;
75
+ const NODE = `http://${IP}:4725`;
76
+ const UDID = 'shared-phone-1';
77
+ /** A live-preview hold on the phone, as formatManualLock writes it. */
78
+ const hold = (userId) => `manual_${userId}_${UDID}`;
79
+ describe("stream services act on this server's own row for a udid", () => {
80
+ let rows;
81
+ let unblock;
82
+ beforeEach(() => {
83
+ rows = [];
84
+ sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
85
+ findDevice: (filter) => __awaiter(void 0, void 0, void 0, function* () {
86
+ var _a;
87
+ return ((_a = rows.find((r) => Object.entries(filter).every(([k, v]) => r[k] === v))) !== null && _a !== void 0 ? _a : null);
88
+ }),
89
+ updateDevice: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }),
90
+ });
91
+ const hub = new PluginContext_1.PluginContext();
92
+ hub.setContext(Object.assign({}, IPluginArgs_1.DefaultPluginArgs, { bindHostOrIp: IP }), 4724, 'hub', '');
93
+ const real = typedi_1.Container.get.bind(typedi_1.Container);
94
+ sinon_1.default.stub(typedi_1.Container, 'get').callsFake((token) => {
95
+ if (token === PluginContext_1.PluginContext)
96
+ return hub;
97
+ if (token === PortAllocator_1.PortAllocator)
98
+ return { release: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }) };
99
+ if (token === recording_store_1.RecordingStore)
100
+ return { isRecording: () => __awaiter(void 0, void 0, void 0, function* () { return false; }) };
101
+ if (token === AndroidH264StreamService_1.default)
102
+ return { getMultiplexer: () => undefined };
103
+ if (token === AndroidStreamService_1.default)
104
+ return { getStreamStatus: () => undefined };
105
+ return real(token);
106
+ });
107
+ unblock = sinon_1.default.stub(deviceService, 'unblockDevice').resolves();
108
+ sinon_1.default.stub(process, 'kill');
109
+ });
110
+ afterEach(() => sinon_1.default.restore());
111
+ function iosService() {
112
+ // Bypass the constructor: it starts watchdog intervals.
113
+ const svc = Object.create(IOSStreamService_1.default.prototype);
114
+ svc.sessions = new Map();
115
+ svc.startFlight = new singleFlight_1.SingleFlight();
116
+ svc.recoveryCooldowns = new Map();
117
+ svc.cleanupOrphanTunnels = () => __awaiter(this, void 0, void 0, function* () { return undefined; });
118
+ return svc;
119
+ }
120
+ it("iOS: stopping the hub's stream never releases the node's preview hold", () => __awaiter(void 0, void 0, void 0, function* () {
121
+ rows = [
122
+ { udid: UDID, host: NODE, platform: 'ios', busy: true, session_id: `manual_usr_bob_${UDID}` },
123
+ { udid: UDID, host: HUB, platform: 'ios', busy: false, session_id: undefined },
124
+ ];
125
+ const svc = iosService();
126
+ svc.sessions.set(UDID, {
127
+ udid: UDID,
128
+ wdaProcess: null,
129
+ forwardWDAProcess: null,
130
+ forwardMJPEGProcess: null,
131
+ tunnelProcess: null,
132
+ wdaPort: 28101,
133
+ mjpegPort: 29101,
134
+ status: 'running',
135
+ lastViewerAt: Date.now(),
136
+ viewerCount: 0,
137
+ });
138
+ yield svc.stopStream(UDID);
139
+ (0, chai_1.expect)(unblock.called, `unblocked ${JSON.stringify(unblock.args)}`).to.equal(false);
140
+ }));
141
+ it("iOS: whether an Appium session holds the phone is read from the hub's own row", () => __awaiter(void 0, void 0, void 0, function* () {
142
+ rows = [
143
+ { udid: UDID, host: NODE, platform: 'ios', busy: false, session_id: undefined },
144
+ { udid: UDID, host: HUB, platform: 'ios', busy: true, session_id: 'hub-appium-session' },
145
+ ];
146
+ (0, chai_1.expect)(yield iosService().appiumSessionMayUse(UDID, 'go-ios tunnels')).to.equal(true);
147
+ }));
148
+ it("Android: a stopping preview never releases the node's preview hold", () => __awaiter(void 0, void 0, void 0, function* () {
149
+ rows = [
150
+ { udid: UDID, host: NODE, platform: 'android', busy: true, session_id: hold('usr_bob') },
151
+ { udid: UDID, host: HUB, platform: 'android', busy: false, session_id: undefined },
152
+ ];
153
+ (0, chai_1.expect)(yield (0, previewHold_1.releaseIdlePreviewHold)(UDID)).to.equal(false);
154
+ (0, chai_1.expect)(unblock.called).to.equal(false);
155
+ }));
156
+ it("Android: the hub's own preview hold is still released", () => __awaiter(void 0, void 0, void 0, function* () {
157
+ rows = [
158
+ { udid: UDID, host: NODE, platform: 'android', busy: false, session_id: undefined },
159
+ { udid: UDID, host: HUB, platform: 'android', busy: true, session_id: hold('usr_al') },
160
+ ];
161
+ (0, chai_1.expect)(yield (0, previewHold_1.releaseIdlePreviewHold)(UDID)).to.equal(true);
162
+ (0, chai_1.expect)(unblock.calledOnceWith(UDID, HUB)).to.equal(true);
163
+ }));
164
+ });
@@ -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 typedi_1 = require("typedi");
54
54
  const control_1 = __importDefault(require("../../src/app/routers/control"));
55
55
  const device_store_1 = require("../../src/data-service/device-store");
@@ -60,6 +60,9 @@ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/io
60
60
  const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
61
61
  const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
62
62
  const authMiddleware_1 = require("../../src/middleware/authMiddleware");
63
+ const container_registration_1 = require("../helpers/container-registration");
64
+ const loopbackServer_1 = require("../helpers/loopbackServer");
65
+ const own_node_id_1 = require("../helpers/own-node-id");
63
66
  /**
64
67
  * Round-trip guard for POST /control/:udid/stream/stop.
65
68
  *
@@ -117,20 +120,28 @@ function buildApp(caller) {
117
120
  return app;
118
121
  }
119
122
  describe('POST /control/:udid/stream/stop — lock ownership round-trip', () => {
123
+ // The fake rows are this server's own phones.
124
+ (0, own_node_id_1.useOwnNodeId)();
120
125
  let unblock;
121
126
  let androidStop;
122
127
  let h264Stop;
123
128
  let deviceRow;
129
+ let restoreContainer;
130
+ // Served on 127.0.0.1: request(app) listens on every address and connects
131
+ // to 127.0.0.1, where another process's listener on that port can answer.
132
+ const loopback = (0, loopbackServer_1.loopbackServers)();
124
133
  function setLock(sessionId) {
125
134
  deviceRow = {
126
135
  udid: UDID,
127
136
  host: '127.0.0.1',
137
+ nodeId: own_node_id_1.OWN_NODE_ID,
128
138
  platform: 'android',
129
139
  busy: sessionId !== null,
130
140
  session_id: sessionId,
131
141
  };
132
142
  }
133
143
  beforeEach(() => {
144
+ restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, AndroidStreamService_1.default, AndroidH264StreamService_1.default, IOSStreamService_1.default, recording_store_1.RecordingStore);
134
145
  setLock(`manual_${ALICE}_${UDID}`);
135
146
  sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
136
147
  findDevice: () => __awaiter(void 0, void 0, void 0, function* () { return deviceRow; }),
@@ -145,11 +156,12 @@ describe('POST /control/:udid/stream/stop — lock ownership round-trip', () =>
145
156
  // Nothing is being recorded here (see stream-stop-recording.spec.ts).
146
157
  typedi_1.Container.set(recording_store_1.RecordingStore, { activeRecordingFor: () => __awaiter(void 0, void 0, void 0, function* () { return null; }) });
147
158
  });
148
- afterEach(() => {
159
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
149
160
  sinon_1.default.restore();
150
- typedi_1.Container.reset();
151
- });
152
- const stop = (caller) => (0, supertest_1.default)(buildApp(caller)).post(`/xenon/api/control/${UDID}/stream/stop`);
161
+ restoreContainer();
162
+ yield loopback.closeAll();
163
+ }));
164
+ const stop = (caller) => __awaiter(void 0, void 0, void 0, function* () { return (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(caller))).post(`/xenon/api/control/${UDID}/stream/stop`); });
153
165
  it('CRITICAL: the header-pair caller who started the stream can stop it', () => __awaiter(void 0, void 0, void 0, function* () {
154
166
  // Exactly what stream/start wrote for this caller: manual_<userId>_<udid>.
155
167
  // req.apiKey.id is set and DIFFERENT — the pre-fix reader picked it and 403'd.
@@ -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 typedi_1 = require("typedi");
54
54
  const control_1 = __importDefault(require("../../src/app/routers/control"));
55
55
  const device_store_1 = require("../../src/data-service/device-store");
@@ -60,6 +60,9 @@ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/io
60
60
  const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
61
61
  const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
62
62
  const authMiddleware_1 = require("../../src/middleware/authMiddleware");
63
+ const container_registration_1 = require("../helpers/container-registration");
64
+ const loopbackServer_1 = require("../helpers/loopbackServer");
65
+ const own_node_id_1 = require("../helpers/own-node-id");
63
66
  /**
64
67
  * POST /control/:udid/stream/stop must not stop the stream under a live
65
68
  * recording. The recording reads from that stream: measured 2026-09-26 on a
@@ -92,17 +95,25 @@ function buildApp(caller) {
92
95
  return app;
93
96
  }
94
97
  describe('POST /control/:udid/stream/stop — a device being recorded', () => {
98
+ // The fake rows are this server's own phones.
99
+ (0, own_node_id_1.useOwnNodeId)();
95
100
  let unblock;
96
101
  let androidStop;
97
102
  let h264Stop;
98
103
  let active;
104
+ let restoreContainer;
105
+ // Served on 127.0.0.1: request(app) listens on every address and connects
106
+ // to 127.0.0.1, where another process's listener on that port can answer.
107
+ const loopback = (0, loopbackServer_1.loopbackServers)();
99
108
  beforeEach(() => {
109
+ restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, AndroidStreamService_1.default, AndroidH264StreamService_1.default, IOSStreamService_1.default, recording_store_1.RecordingStore);
100
110
  active = { id: 'rec-1', groupId: 'grp-1' };
101
111
  sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
102
112
  findDevice: () => __awaiter(void 0, void 0, void 0, function* () {
103
113
  return ({
104
114
  udid: UDID,
105
115
  host: '127.0.0.1',
116
+ nodeId: own_node_id_1.OWN_NODE_ID,
106
117
  platform: 'android',
107
118
  busy: true,
108
119
  session_id: `manual_${ALICE}_${UDID}`,
@@ -118,11 +129,12 @@ describe('POST /control/:udid/stream/stop — a device being recorded', () => {
118
129
  typedi_1.Container.set(IOSStreamService_1.default, { stopStream: sinon_1.default.stub().resolves() });
119
130
  typedi_1.Container.set(recording_store_1.RecordingStore, { activeRecordingFor: () => __awaiter(void 0, void 0, void 0, function* () { return active; }) });
120
131
  });
121
- afterEach(() => {
132
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
122
133
  sinon_1.default.restore();
123
- typedi_1.Container.reset();
124
- });
125
- const stop = (caller) => (0, supertest_1.default)(buildApp(caller)).post(`/xenon/api/control/${UDID}/stream/stop`);
134
+ restoreContainer();
135
+ yield loopback.closeAll();
136
+ }));
137
+ const stop = (caller) => __awaiter(void 0, void 0, void 0, function* () { return (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(caller))).post(`/xenon/api/control/${UDID}/stream/stop`); });
126
138
  it('refuses the owner with 409 and names the recording to stop instead', () => __awaiter(void 0, void 0, void 0, function* () {
127
139
  const res = yield stop({ userId: ALICE });
128
140
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(409);
@@ -16,7 +16,7 @@ require("reflect-metadata");
16
16
  const chai_1 = require("chai");
17
17
  const sinon_1 = __importDefault(require("sinon"));
18
18
  const express_1 = __importDefault(require("express"));
19
- const supertest_1 = __importDefault(require("supertest"));
19
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
20
20
  const typedi_1 = require("typedi");
21
21
  const fs_1 = __importDefault(require("fs"));
22
22
  const os_1 = __importDefault(require("os"));
@@ -29,6 +29,9 @@ const UserService_1 = require("../../src/services/UserService");
29
29
  const ApiKeyService_1 = require("../../src/services/ApiKeyService");
30
30
  const UserSessionService_1 = require("../../src/services/UserSessionService");
31
31
  const device_store_1 = require("../../src/data-service/device-store");
32
+ const container_registration_1 = require("../helpers/container-registration");
33
+ const loopbackServer_1 = require("../helpers/loopbackServer");
34
+ const own_node_id_1 = require("../helpers/own-node-id");
32
35
  /**
33
36
  * `req.auth.userId` must always hold a User id.
34
37
  *
@@ -49,6 +52,8 @@ const UDID = 'UDID-1';
49
52
  const ALICE_USER = 'usr_alice';
50
53
  const ALICE_KEY = 'key_abc';
51
54
  describe('stream/ticket carries the user identity, not the credential', () => {
55
+ // The fake rows are this server's own phones.
56
+ (0, own_node_id_1.useOwnNodeId)();
52
57
  let dir;
53
58
  let ticketSvc;
54
59
  function buildApp(caller = {}) {
@@ -78,7 +83,12 @@ describe('stream/ticket carries the user identity, not the credential', () => {
78
83
  app.use('/xenon/api', apiRouter);
79
84
  return app;
80
85
  }
86
+ let restoreContainer;
87
+ // Served on 127.0.0.1: request(app) listens on every address and connects
88
+ // to 127.0.0.1, where another process's listener on that port can answer.
89
+ const loopback = (0, loopbackServer_1.loopbackServers)();
81
90
  beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
91
+ restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService, StreamTicketService_1.StreamTicketService, UserService_1.UserService, ApiKeyService_1.ApiKeyService, UserSessionService_1.UserSessionService);
82
92
  dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-ticket-identity-'));
83
93
  const keySvc = new JwtKeyService_1.JwtKeyService();
84
94
  yield keySvc.init(dir);
@@ -96,17 +106,27 @@ describe('stream/ticket carries the user identity, not the credential', () => {
96
106
  sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
97
107
  findDevice: (_a) => __awaiter(void 0, [_a], void 0, function* ({ udid }) {
98
108
  return udid === UDID
99
- ? { udid, host: 'h', platform: 'android', screenWidth: '1080', screenHeight: '1920' }
109
+ ? {
110
+ udid,
111
+ host: 'h',
112
+ nodeId: own_node_id_1.OWN_NODE_ID,
113
+ platform: 'android',
114
+ screenWidth: '1080',
115
+ screenHeight: '1920',
116
+ }
100
117
  : null;
101
118
  }),
102
119
  });
103
120
  }));
104
- afterEach(() => {
121
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
105
122
  sinon_1.default.restore();
106
- typedi_1.Container.reset();
123
+ restoreContainer();
124
+ yield loopback.closeAll();
107
125
  fs_1.default.rmSync(dir, { recursive: true, force: true });
126
+ }));
127
+ const mint = (caller) => __awaiter(void 0, void 0, void 0, function* () {
128
+ return (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(caller))).post(`/xenon/api/control/${UDID}/stream/ticket`);
108
129
  });
109
- const mint = (caller) => (0, supertest_1.default)(buildApp(caller)).post(`/xenon/api/control/${UDID}/stream/ticket`);
110
130
  it('mints with the userId even when a different apiKey id is present', () => __awaiter(void 0, void 0, void 0, function* () {
111
131
  const res = yield mint({ userId: ALICE_USER, apiKeyId: ALICE_KEY });
112
132
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
@@ -150,7 +170,7 @@ describe('stream/ticket carries the user identity, not the credential', () => {
150
170
  // two must match: minting for any udid would mark the ones that 404 as
151
171
  // phones another team owns.
152
172
  it("404s a udid that isn't a device, and mints nothing", () => __awaiter(void 0, void 0, void 0, function* () {
153
- const res = yield (0, supertest_1.default)(buildApp({ userId: ALICE_USER })).post('/xenon/api/control/NO-SUCH-DEVICE/stream/ticket');
173
+ const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp({ userId: ALICE_USER }))).post('/xenon/api/control/NO-SUCH-DEVICE/stream/ticket');
154
174
  (0, chai_1.expect)(res.status).to.equal(404);
155
175
  (0, chai_1.expect)(res.text).to.equal('Device not found');
156
176
  }));
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ require("reflect-metadata");
7
+ const chai_1 = require("chai");
8
+ const sinon_1 = __importDefault(require("sinon"));
9
+ const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
10
+ const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
11
+ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
12
+ /**
13
+ * The stream services start their watchdogs in their constructors. On a
14
+ * server the HTTP listener keeps the process alive, so those intervals only
15
+ * need to fire while it runs; referenced, they also kept a test run (or any
16
+ * script that merely constructs a service) alive after its work was done.
17
+ */
18
+ describe('stream service watchdogs', () => {
19
+ let created;
20
+ beforeEach(() => {
21
+ created = [];
22
+ const realSetInterval = global.setInterval;
23
+ sinon_1.default.stub(global, 'setInterval').callsFake(((fn, ms) => {
24
+ const timer = realSetInterval(fn, ms);
25
+ created.push(timer);
26
+ return timer;
27
+ }));
28
+ });
29
+ afterEach(() => {
30
+ sinon_1.default.restore();
31
+ for (const timer of created)
32
+ clearInterval(timer);
33
+ });
34
+ for (const [name, Service] of [
35
+ ['AndroidStreamService', AndroidStreamService_1.default],
36
+ ['AndroidH264StreamService', AndroidH264StreamService_1.default],
37
+ ['IOSStreamService', IOSStreamService_1.default],
38
+ ]) {
39
+ it(`${name}'s intervals don't keep the process alive`, () => {
40
+ new Service();
41
+ (0, chai_1.expect)(created.length, 'the constructor starts its watchdog').to.be.greaterThan(0);
42
+ for (const timer of created)
43
+ (0, chai_1.expect)(timer.hasRef(), 'unref()ed').to.equal(false);
44
+ });
45
+ }
46
+ });