@xenon-device-management/xenon 2.0.0 → 2.1.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 (279) hide show
  1. package/README.md +2 -1
  2. package/lib/package.json +1 -2
  3. package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-C-Pw_hvw.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-DmYQ0oSk.js} +1 -1
  5. package/lib/public/assets/BugReportButton-n8T1dFUp.js +10 -0
  6. package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-D-rGOBbJ.js} +1 -1
  7. package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-DExETevp.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-CCdFlZm6.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-DiUSOSL-.js} +1 -1
  10. package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-ZOfpH6gh.js} +1 -1
  11. package/lib/public/assets/{Modal-CceoMY7D.js → Modal-CZNtoB-1.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-BKp6h8Jl.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-Bf881FX4.js} +1 -1
  14. package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-EUoyizvW.js} +1 -1
  15. package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-ClT4hf4_.js} +1 -1
  16. package/lib/public/assets/{Table-LV7zFyZg.js → Table-ucR4bnmc.js} +1 -1
  17. package/lib/public/assets/{activity-D17TUloi.js → activity-K-hdylwq.js} +1 -1
  18. package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-BLhdwI3c.js} +1 -1
  19. package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-BbiN7rBI.js} +1 -1
  20. package/lib/public/assets/{apps-Dbnzd0fW.js → apps-Dez1petz.js} +1 -1
  21. package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-UjmFHLAJ.js} +1 -1
  22. package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-BVyEigbu.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-B-ZlBof3.js} +1 -1
  24. package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-DAQjAgUv.js} +2 -2
  25. package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-CmIPa5JK.js} +1 -1
  26. package/lib/public/assets/{button-xtAhYR-f.js → button-Dd-IhMC1.js} +1 -1
  27. package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-CAIeJwa5.js} +1 -1
  28. package/lib/public/assets/{check-CyuEoKkd.js → check-C4Jrjg8f.js} +1 -1
  29. package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-BGzlf-4i.js} +1 -1
  30. package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-_wYqDjDC.js} +1 -1
  31. package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-Ckn00yMB.js} +1 -1
  32. package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-pdVkomLa.js} +1 -1
  33. package/lib/public/assets/{clock-Cd-t7xpe.js → clock-B8vmwkHj.js} +1 -1
  34. package/lib/public/assets/{copy-C6zerX3I.js → copy-BAqnFI-F.js} +1 -1
  35. package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CYq_4F_N.js} +3 -3
  36. package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
  37. package/lib/public/assets/{download-x9WP7mcL.js → download-B6UEAp_1.js} +1 -1
  38. package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-CLqNvmrN.js} +1 -1
  39. package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-Da90BBbT.js} +1 -1
  40. package/lib/public/assets/{index-BR1wTIPC.js → index-BuIIcmGp.js} +2 -2
  41. package/lib/public/assets/{index-BvH_ekRy.js → index-MX7IzqyW.js} +1 -1
  42. package/lib/public/assets/{input-DejcCiTI.js → input-Bq9aY-We.js} +1 -1
  43. package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CIhXIJyP.js} +1 -1
  44. package/lib/public/assets/{layers-BKqYLiHE.js → layers-bXjpswQI.js} +1 -1
  45. package/lib/public/assets/{lock-LFsFhc17.js → lock-pJ72y5Fy.js} +1 -1
  46. package/lib/public/assets/{login-x2pkzt-t.js → login-UOcHALV2.js} +1 -1
  47. package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-XnJgp5yb.js} +1 -1
  48. package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-QkAhI1Jx.js} +1 -1
  49. package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-B42Da0m4.js} +1 -1
  50. package/lib/public/assets/{network-Z_zBhpsD.js → network-CKVqcyp6.js} +1 -1
  51. package/lib/public/assets/{overview-BY5uv08S.js → overview-BORGkN3N.js} +1 -1
  52. package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CKWFOjOI.js} +1 -1
  53. package/lib/public/assets/{plus-BTD0dnQ0.js → plus-BpFKVdC8.js} +1 -1
  54. package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-CcbKBicY.js} +1 -1
  55. package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BA0W9bxl.js} +1 -1
  56. package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Cp7ZBq3s.js} +1 -1
  57. package/lib/public/assets/{select-Uc9E5pye.js → select-DIZWIoVR.js} +1 -1
  58. package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C_4YuvAp.js} +1 -1
  59. package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CXjD_Xth.js} +1 -1
  60. package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CTqIHeoG.js} +1 -1
  61. package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-B0KYkk8z.js} +1 -1
  62. package/lib/public/assets/{settings-CkSWiZq8.js → settings-BcRBKUXd.js} +1 -1
  63. package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-Dh2PXSf_.js} +1 -1
  64. package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-_sXqZztt.js} +1 -1
  65. package/lib/public/assets/{teams-PpoEQz31.js → teams-DGHT8MKm.js} +1 -1
  66. package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV88cV3Z.js} +1 -1
  67. package/lib/public/assets/{upload-C3i7sAZl.js → upload-CIDH9wkj.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-DYGuSNwl.js} +1 -1
  69. package/lib/public/assets/{users-CEJ3fC78.js → users-7yviqflc.js} +1 -1
  70. package/lib/public/assets/{users-Drd7ifJx.js → users-BUE7MoTq.js} +1 -1
  71. package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-BTRxtlGo.js} +1 -1
  72. package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-DMKD9Pgm.js} +1 -1
  73. package/lib/public/assets/{zap-4yN8Jdfw.js → zap-CWE94qcK.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 +57 -13
  82. package/lib/src/app/routers/grid.js +28 -8
  83. package/lib/src/app/ws/h264StreamWs.js +13 -8
  84. package/lib/src/app/ws/logcatWs.js +12 -7
  85. package/lib/src/app/ws/upgradeRouter.js +215 -0
  86. package/lib/src/dashboard/event-manager.js +7 -6
  87. package/lib/src/data-service/device-service.js +152 -49
  88. package/lib/src/data-service/device-store.js +81 -10
  89. package/lib/src/data-service/deviceClaims.js +98 -0
  90. package/lib/src/data-service/deviceFieldOwners.js +32 -10
  91. package/lib/src/data-service/prisma-store.js +94 -31
  92. package/lib/src/device-managers/AndroidDeviceManager.js +13 -15
  93. package/lib/src/device-managers/HealthMonitorService.js +17 -2
  94. package/lib/src/device-managers/IOSDeviceManager.js +5 -1
  95. package/lib/src/device-managers/NodeDevices.js +7 -2
  96. package/lib/src/device-managers/android/AndroidH264StreamService.js +3 -3
  97. package/lib/src/device-managers/android/AndroidStreamService.js +6 -5
  98. package/lib/src/device-managers/android/previewHold.js +2 -2
  99. package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
  100. package/lib/src/device-managers/ios/IOSStreamService.js +158 -38
  101. package/lib/src/device-managers/localDeviceHosts.js +86 -0
  102. package/lib/src/device-managers/ownDeviceRow.js +38 -0
  103. package/lib/src/device-utils.js +78 -24
  104. package/lib/src/gateway/createHandoff.js +73 -0
  105. package/lib/src/gateway/defaultGateway.js +80 -0
  106. package/lib/src/gateway/forwardToNode.js +147 -0
  107. package/lib/src/gateway/hubSessionToken.js +356 -0
  108. package/lib/src/gateway/internalCall.js +90 -0
  109. package/lib/src/gateway/nodeSessionStatus.js +140 -0
  110. package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
  111. package/lib/src/gateway/sessionCreate.js +136 -0
  112. package/lib/src/gateway/sessionGateway.js +201 -0
  113. package/lib/src/gateway/sessionLocator.js +100 -0
  114. package/lib/src/generated/client/edge.js +7 -4
  115. package/lib/src/generated/client/index-browser.js +4 -1
  116. package/lib/src/generated/client/index.d.ts +112 -1
  117. package/lib/src/generated/client/index.js +7 -4
  118. package/lib/src/generated/client/package.json +1 -1
  119. package/lib/src/generated/client/schema.prisma +9 -0
  120. package/lib/src/generated/client/wasm.js +4 -1
  121. package/lib/src/helpers/index.js +5 -1
  122. package/lib/src/logger.js +5 -0
  123. package/lib/src/middleware/authMiddleware.js +80 -0
  124. package/lib/src/middleware/commandAuth.js +31 -17
  125. package/lib/src/middleware/csrfMiddleware.js +3 -2
  126. package/lib/src/middleware/sessionListingFilter.js +153 -0
  127. package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
  128. package/lib/src/plugin.js +27 -1
  129. package/lib/src/prisma.js +2 -1
  130. package/lib/src/services/DeviceReconciler.js +5 -2
  131. package/lib/src/services/OrphanSweeper.js +12 -19
  132. package/lib/src/services/PortAllocator.js +75 -16
  133. package/lib/src/services/ServerManager.js +50 -30
  134. package/lib/src/services/SessionLifecycleService.js +335 -76
  135. package/lib/src/services/ShutdownCoordinator.js +7 -1
  136. package/lib/src/services/SocketServer.js +11 -2
  137. package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
  138. package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
  139. package/lib/src/services/lease/LeaseOrphanSweeper.js +32 -6
  140. package/lib/src/services/lease/LeaseService.js +19 -5
  141. package/lib/src/services/session/nodeCreateCaps.js +55 -0
  142. package/lib/src/services/session/sessionCredentials.js +7 -59
  143. package/lib/src/sessions/CloudSession.js +12 -0
  144. package/lib/src/sessions/LocalSession.js +52 -26
  145. package/lib/src/sessions/RemoteSession.js +116 -9
  146. package/lib/src/sessions/SessionManager.js +49 -6
  147. package/lib/src/sessions/appiumUmbrella.js +32 -0
  148. package/lib/test/helpers/appium-umbrella.js +84 -0
  149. package/lib/test/helpers/fake-device-table.js +97 -0
  150. package/lib/test/helpers/loki-stores.js +41 -0
  151. package/lib/test/helpers/loopbackRequest.js +77 -0
  152. package/lib/test/helpers/scratch-database.js +100 -0
  153. package/lib/test/helpers/scratch-port-leases.js +119 -0
  154. package/lib/test/helpers/test-container.js +35 -27
  155. package/lib/test/integration/androidDevices.spec.js +5 -4
  156. package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
  157. package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
  158. package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
  159. package/lib/test/unit/AndroidDeviceManager.spec.js +8 -1
  160. package/lib/test/unit/DeviceModel.spec.js +9 -9
  161. package/lib/test/unit/HealingCascade.test.js +5 -1
  162. package/lib/test/unit/OrphanSweeper.test.js +17 -13
  163. package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
  164. package/lib/test/unit/StreamTicketService.spec.js +4 -1
  165. package/lib/test/unit/TokenEndpoint.spec.js +17 -3
  166. package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
  167. package/lib/test/unit/android-h264-service.spec.js +6 -0
  168. package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
  169. package/lib/test/unit/apps-team-visibility.spec.js +16 -16
  170. package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
  171. package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
  172. package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
  173. package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
  174. package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
  175. package/lib/test/unit/authMiddleware.test.js +3 -0
  176. package/lib/test/unit/bug-report/route.spec.js +9 -9
  177. package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
  178. package/lib/test/unit/clipboard-route.spec.js +18 -8
  179. package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
  180. package/lib/test/unit/command-auth.spec.js +31 -31
  181. package/lib/test/unit/control-device-lookup.spec.js +9 -9
  182. package/lib/test/unit/control-forward-decision.spec.js +115 -0
  183. package/lib/test/unit/control-lookup-failure.spec.js +3 -3
  184. package/lib/test/unit/create-error-message.spec.js +43 -0
  185. package/lib/test/unit/create-handoff.spec.js +86 -0
  186. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  187. package/lib/test/unit/device-access-guard.spec.js +28 -28
  188. package/lib/test/unit/device-service.spec.js +4 -0
  189. package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
  190. package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
  191. package/lib/test/unit/device-team-guard.spec.js +20 -20
  192. package/lib/test/unit/device-utils.spec.js +8 -0
  193. package/lib/test/unit/extract-team-cap.spec.js +65 -0
  194. package/lib/test/unit/grid-admin-only.spec.js +3 -3
  195. package/lib/test/unit/healing-tier-gate.spec.js +5 -1
  196. package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
  197. package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
  198. package/lib/test/unit/helpers.spec.js +27 -0
  199. package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
  200. package/lib/test/unit/hub-create-once.spec.js +190 -0
  201. package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
  202. package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
  203. package/lib/test/unit/hub-node-control-forward.spec.js +291 -0
  204. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
  205. package/lib/test/unit/hub-node-race.spec.js +401 -0
  206. package/lib/test/unit/hub-node-same-host.spec.js +356 -0
  207. package/lib/test/unit/hub-register-removal.spec.js +165 -0
  208. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
  209. package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
  210. package/lib/test/unit/hub-session-token.spec.js +265 -0
  211. package/lib/test/unit/insert-before-routes.spec.js +5 -5
  212. package/lib/test/unit/install-repository-app-team.spec.js +2 -2
  213. package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
  214. package/lib/test/unit/internal-call.spec.js +181 -0
  215. package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
  216. package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
  217. package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
  218. package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
  219. package/lib/test/unit/is-xenon-running.spec.js +69 -0
  220. package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
  221. package/lib/test/unit/lease/LeaseService.spec.js +28 -1
  222. package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
  223. package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +92 -0
  224. package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
  225. package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
  226. package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
  227. package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
  228. package/lib/test/unit/logcat-ws.spec.js +4 -1
  229. package/lib/test/unit/loopback-request.spec.js +81 -0
  230. package/lib/test/unit/node-create-caps.spec.js +54 -0
  231. package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
  232. package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
  233. package/lib/test/unit/omni-vision.spec.js +7 -3
  234. package/lib/test/unit/plugin.spec.js +45 -4
  235. package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
  236. package/lib/test/unit/profile-router.test.js +11 -11
  237. package/lib/test/unit/queue-requester.spec.js +5 -0
  238. package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
  239. package/lib/test/unit/queue-visibility.spec.js +1 -1
  240. package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
  241. package/lib/test/unit/recording-instrumentation.spec.js +5 -0
  242. package/lib/test/unit/recording-source-ended.spec.js +5 -0
  243. package/lib/test/unit/recording-store.spec.js +9 -3
  244. package/lib/test/unit/recordings-library-routes.spec.js +51 -51
  245. package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
  246. package/lib/test/unit/remote-session-create.spec.js +130 -0
  247. package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
  248. package/lib/test/unit/reset-link.test.js +14 -14
  249. package/lib/test/unit/session-app-resolution.spec.js +14 -10
  250. package/lib/test/unit/session-assets.spec.js +9 -9
  251. package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
  252. package/lib/test/unit/session-create-layer.spec.js +255 -0
  253. package/lib/test/unit/session-credential-strip.spec.js +25 -9
  254. package/lib/test/unit/session-credentials.spec.js +0 -47
  255. package/lib/test/unit/session-listing-auth.spec.js +345 -0
  256. package/lib/test/unit/session-locator.spec.js +250 -0
  257. package/lib/test/unit/session-node-calls.spec.js +174 -0
  258. package/lib/test/unit/session-owner-resolver.spec.js +107 -0
  259. package/lib/test/unit/session-recovery-routing.spec.js +118 -0
  260. package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
  261. package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
  262. package/lib/test/unit/socketServer.bearer.spec.js +3 -1
  263. package/lib/test/unit/stream-leave-route.spec.js +15 -7
  264. package/lib/test/unit/stream-own-device-row.spec.js +164 -0
  265. package/lib/test/unit/stream-stop-ownership.spec.js +13 -5
  266. package/lib/test/unit/stream-stop-recording.spec.js +13 -5
  267. package/lib/test/unit/stream-ticket-identity.spec.js +15 -5
  268. package/lib/test/unit/upgrade-router.spec.js +446 -0
  269. package/lib/test/unit/users-router.test.js +11 -8
  270. package/lib/test/unit/verify-credential.spec.js +4 -1
  271. package/lib/test/unit/webdriver-info-route.spec.js +64 -0
  272. package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
  273. package/lib/tsconfig.tsbuildinfo +1 -1
  274. package/package.json +1 -2
  275. package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
  276. package/prisma/schema.prisma +9 -0
  277. package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
  278. package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
  279. 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,8 @@ 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");
63
65
  const UDID = 'DEV-1';
64
66
  const ALICE = 'usr_alice';
65
67
  const BOB = 'usr_bob';
@@ -90,7 +92,12 @@ describe('POST /control/:udid/stream/leave', () => {
90
92
  let h264Clients;
91
93
  let mjpegViewers;
92
94
  let iosViewers;
95
+ let restoreContainer;
96
+ // Served on 127.0.0.1: request(app) listens on every address and connects
97
+ // to 127.0.0.1, where another process's listener on that port can answer.
98
+ const loopback = (0, loopbackServer_1.loopbackServers)();
93
99
  beforeEach(() => {
100
+ restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, AndroidStreamService_1.default, AndroidH264StreamService_1.default, IOSStreamService_1.default, recording_store_1.RecordingStore);
94
101
  deviceRow = {
95
102
  udid: UDID,
96
103
  host: '127.0.0.1',
@@ -126,14 +133,15 @@ describe('POST /control/:udid/stream/leave', () => {
126
133
  isRecording: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
127
134
  });
128
135
  });
129
- afterEach(() => {
136
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
130
137
  sinon_1.default.restore();
131
- typedi_1.Container.reset();
132
- });
138
+ restoreContainer();
139
+ yield loopback.closeAll();
140
+ }));
133
141
  describe('the route', () => {
134
142
  it('schedules the leave for the holder and answers at once', () => __awaiter(void 0, void 0, void 0, function* () {
135
143
  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`);
144
+ const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(ALICE))).post(`/xenon/api/control/${UDID}/stream/leave`);
137
145
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(202);
138
146
  (0, chai_1.expect)(leave.calledOnceWith(UDID)).to.equal(true);
139
147
  // Nothing is stopped yet: that waits for the grace and the viewer count.
@@ -141,14 +149,14 @@ describe('POST /control/:udid/stream/leave', () => {
141
149
  }));
142
150
  it('refuses someone else’s hold', () => __awaiter(void 0, void 0, void 0, function* () {
143
151
  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`);
152
+ const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(BOB))).post(`/xenon/api/control/${UDID}/stream/leave`);
145
153
  (0, chai_1.expect)(res.status).to.equal(403);
146
154
  (0, chai_1.expect)(leave.called).to.equal(false);
147
155
  }));
148
156
  // A reload leaves and then starts again; the start must win.
149
157
  it('stream/start cancels a pending leave', () => __awaiter(void 0, void 0, void 0, function* () {
150
158
  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`);
159
+ yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(ALICE))).post(`/xenon/api/control/${UDID}/stream/start`);
152
160
  (0, chai_1.expect)(cancel.calledWith(UDID)).to.equal(true);
153
161
  }));
154
162
  });
@@ -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,8 @@ 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");
63
65
  /**
64
66
  * Round-trip guard for POST /control/:udid/stream/stop.
65
67
  *
@@ -121,6 +123,10 @@ describe('POST /control/:udid/stream/stop — lock ownership round-trip', () =>
121
123
  let androidStop;
122
124
  let h264Stop;
123
125
  let deviceRow;
126
+ let restoreContainer;
127
+ // Served on 127.0.0.1: request(app) listens on every address and connects
128
+ // to 127.0.0.1, where another process's listener on that port can answer.
129
+ const loopback = (0, loopbackServer_1.loopbackServers)();
124
130
  function setLock(sessionId) {
125
131
  deviceRow = {
126
132
  udid: UDID,
@@ -131,6 +137,7 @@ describe('POST /control/:udid/stream/stop — lock ownership round-trip', () =>
131
137
  };
132
138
  }
133
139
  beforeEach(() => {
140
+ restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, AndroidStreamService_1.default, AndroidH264StreamService_1.default, IOSStreamService_1.default, recording_store_1.RecordingStore);
134
141
  setLock(`manual_${ALICE}_${UDID}`);
135
142
  sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
136
143
  findDevice: () => __awaiter(void 0, void 0, void 0, function* () { return deviceRow; }),
@@ -145,11 +152,12 @@ describe('POST /control/:udid/stream/stop — lock ownership round-trip', () =>
145
152
  // Nothing is being recorded here (see stream-stop-recording.spec.ts).
146
153
  typedi_1.Container.set(recording_store_1.RecordingStore, { activeRecordingFor: () => __awaiter(void 0, void 0, void 0, function* () { return null; }) });
147
154
  });
148
- afterEach(() => {
155
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
149
156
  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`);
157
+ restoreContainer();
158
+ yield loopback.closeAll();
159
+ }));
160
+ 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
161
  it('CRITICAL: the header-pair caller who started the stream can stop it', () => __awaiter(void 0, void 0, void 0, function* () {
154
162
  // Exactly what stream/start wrote for this caller: manual_<userId>_<udid>.
155
163
  // 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,8 @@ 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");
63
65
  /**
64
66
  * POST /control/:udid/stream/stop must not stop the stream under a live
65
67
  * recording. The recording reads from that stream: measured 2026-09-26 on a
@@ -96,7 +98,12 @@ describe('POST /control/:udid/stream/stop — a device being recorded', () => {
96
98
  let androidStop;
97
99
  let h264Stop;
98
100
  let active;
101
+ let restoreContainer;
102
+ // Served on 127.0.0.1: request(app) listens on every address and connects
103
+ // to 127.0.0.1, where another process's listener on that port can answer.
104
+ const loopback = (0, loopbackServer_1.loopbackServers)();
99
105
  beforeEach(() => {
106
+ restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, AndroidStreamService_1.default, AndroidH264StreamService_1.default, IOSStreamService_1.default, recording_store_1.RecordingStore);
100
107
  active = { id: 'rec-1', groupId: 'grp-1' };
101
108
  sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
102
109
  findDevice: () => __awaiter(void 0, void 0, void 0, function* () {
@@ -118,11 +125,12 @@ describe('POST /control/:udid/stream/stop — a device being recorded', () => {
118
125
  typedi_1.Container.set(IOSStreamService_1.default, { stopStream: sinon_1.default.stub().resolves() });
119
126
  typedi_1.Container.set(recording_store_1.RecordingStore, { activeRecordingFor: () => __awaiter(void 0, void 0, void 0, function* () { return active; }) });
120
127
  });
121
- afterEach(() => {
128
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
122
129
  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`);
130
+ restoreContainer();
131
+ yield loopback.closeAll();
132
+ }));
133
+ 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
134
  it('refuses the owner with 409 and names the recording to stop instead', () => __awaiter(void 0, void 0, void 0, function* () {
127
135
  const res = yield stop({ userId: ALICE });
128
136
  (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,8 @@ 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");
32
34
  /**
33
35
  * `req.auth.userId` must always hold a User id.
34
36
  *
@@ -78,7 +80,12 @@ describe('stream/ticket carries the user identity, not the credential', () => {
78
80
  app.use('/xenon/api', apiRouter);
79
81
  return app;
80
82
  }
83
+ let restoreContainer;
84
+ // Served on 127.0.0.1: request(app) listens on every address and connects
85
+ // to 127.0.0.1, where another process's listener on that port can answer.
86
+ const loopback = (0, loopbackServer_1.loopbackServers)();
81
87
  beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
88
+ restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService, StreamTicketService_1.StreamTicketService, UserService_1.UserService, ApiKeyService_1.ApiKeyService, UserSessionService_1.UserSessionService);
82
89
  dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-ticket-identity-'));
83
90
  const keySvc = new JwtKeyService_1.JwtKeyService();
84
91
  yield keySvc.init(dir);
@@ -101,12 +108,15 @@ describe('stream/ticket carries the user identity, not the credential', () => {
101
108
  }),
102
109
  });
103
110
  }));
104
- afterEach(() => {
111
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
105
112
  sinon_1.default.restore();
106
- typedi_1.Container.reset();
113
+ restoreContainer();
114
+ yield loopback.closeAll();
107
115
  fs_1.default.rmSync(dir, { recursive: true, force: true });
116
+ }));
117
+ const mint = (caller) => __awaiter(void 0, void 0, void 0, function* () {
118
+ return (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(caller))).post(`/xenon/api/control/${UDID}/stream/ticket`);
108
119
  });
109
- const mint = (caller) => (0, supertest_1.default)(buildApp(caller)).post(`/xenon/api/control/${UDID}/stream/ticket`);
110
120
  it('mints with the userId even when a different apiKey id is present', () => __awaiter(void 0, void 0, void 0, function* () {
111
121
  const res = yield mint({ userId: ALICE_USER, apiKeyId: ALICE_KEY });
112
122
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
@@ -150,7 +160,7 @@ describe('stream/ticket carries the user identity, not the credential', () => {
150
160
  // two must match: minting for any udid would mark the ones that 404 as
151
161
  // phones another team owns.
152
162
  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');
163
+ const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp({ userId: ALICE_USER }))).post('/xenon/api/control/NO-SUCH-DEVICE/stream/ticket');
154
164
  (0, chai_1.expect)(res.status).to.equal(404);
155
165
  (0, chai_1.expect)(res.text).to.equal('Device not found');
156
166
  }));