@xenon-device-management/xenon 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/README.md +2 -1
  2. package/lib/package.json +3 -4
  3. package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-XpewX5nn.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-BQDv3ZCC.js} +1 -1
  5. package/lib/public/assets/BugReportButton-C7OmS61B.js +10 -0
  6. package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-wl3l2Zlt.js} +1 -1
  7. package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-fPxujqqE.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-5bCutWrs.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-owsBiDT1.js} +1 -1
  10. package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-DFhqzPSt.js} +1 -1
  11. package/lib/public/assets/{Modal-CceoMY7D.js → Modal-Bw-a-E-e.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-WtByf5bC.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-FpWWFNgT.js} +1 -1
  14. package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-Jzn9c7o8.js} +1 -1
  15. package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-BR7nOhpT.js} +1 -1
  16. package/lib/public/assets/{Table-LV7zFyZg.js → Table-B_Va3V05.js} +1 -1
  17. package/lib/public/assets/{activity-D17TUloi.js → activity-DxRFWvkN.js} +1 -1
  18. package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-CQf-9_V6.js} +1 -1
  19. package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-SoE1Kd7N.js} +1 -1
  20. package/lib/public/assets/{apps-Dbnzd0fW.js → apps-CgGXTlgn.js} +1 -1
  21. package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-uQ6WIwiC.js} +1 -1
  22. package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-C9RyfzXS.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-DFHi_8yG.js} +1 -1
  24. package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-Bii1t4qc.js} +2 -2
  25. package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-Bnv8Z9hi.js} +1 -1
  26. package/lib/public/assets/{button-xtAhYR-f.js → button-_Y78wXiP.js} +1 -1
  27. package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-D4MqSOc2.js} +1 -1
  28. package/lib/public/assets/{check-CyuEoKkd.js → check-uGHCXe-L.js} +1 -1
  29. package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-Dy1OqpzB.js} +1 -1
  30. package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-BUsldEli.js} +1 -1
  31. package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-D3Qq0tKp.js} +1 -1
  32. package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-B4IOD1DE.js} +1 -1
  33. package/lib/public/assets/{clock-Cd-t7xpe.js → clock-BfYfzvSg.js} +1 -1
  34. package/lib/public/assets/{copy-C6zerX3I.js → copy-DZwYE5Va.js} +1 -1
  35. package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CRG4Utt5.js} +3 -3
  36. package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
  37. package/lib/public/assets/{download-x9WP7mcL.js → download-CCDYo_TN.js} +1 -1
  38. package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-DuLCmVFb.js} +1 -1
  39. package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-CtKNPqzb.js} +1 -1
  40. package/lib/public/assets/{index-BR1wTIPC.js → index-3uxl3o7K.js} +43 -43
  41. package/lib/public/assets/{index-BvH_ekRy.js → index-Zy1Op5C1.js} +1 -1
  42. package/lib/public/assets/{input-DejcCiTI.js → input-CzTWvUXZ.js} +1 -1
  43. package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CI-nYDFM.js} +1 -1
  44. package/lib/public/assets/{layers-BKqYLiHE.js → layers-YZZGFyTX.js} +1 -1
  45. package/lib/public/assets/{lock-LFsFhc17.js → lock-L8tzWgDo.js} +1 -1
  46. package/lib/public/assets/{login-x2pkzt-t.js → login-DBbX-fu8.js} +1 -1
  47. package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-AJwkOIsE.js} +1 -1
  48. package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-Di_66F2m.js} +1 -1
  49. package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-Cnab2zdg.js} +1 -1
  50. package/lib/public/assets/{network-Z_zBhpsD.js → network-DiSumY-1.js} +1 -1
  51. package/lib/public/assets/{overview-BY5uv08S.js → overview-CwE4gXl5.js} +1 -1
  52. package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CfR4Sm9V.js} +1 -1
  53. package/lib/public/assets/{plus-BTD0dnQ0.js → plus-DOS8P9CQ.js} +1 -1
  54. package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-BYpHwQIH.js} +1 -1
  55. package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BRVJU_qV.js} +1 -1
  56. package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Bi3JvAFZ.js} +1 -1
  57. package/lib/public/assets/{select-Uc9E5pye.js → select-Cqx7pEou.js} +1 -1
  58. package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C0DsncKj.js} +1 -1
  59. package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CAhpdilF.js} +1 -1
  60. package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CrCVeW2m.js} +1 -1
  61. package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-F_hwYqEt.js} +1 -1
  62. package/lib/public/assets/{settings-CkSWiZq8.js → settings-C-s5pypL.js} +1 -1
  63. package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-DY5uzv6A.js} +1 -1
  64. package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-Dli7gwyg.js} +1 -1
  65. package/lib/public/assets/{teams-PpoEQz31.js → teams-ui4IhHNC.js} +1 -1
  66. package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV4hSmXJ.js} +1 -1
  67. package/lib/public/assets/{upload-C3i7sAZl.js → upload-DkGy_J-L.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-BFtMqQOh.js} +1 -1
  69. package/lib/public/assets/{users-Drd7ifJx.js → users-BU0nXIVe.js} +1 -1
  70. package/lib/public/assets/{users-CEJ3fC78.js → users-Nb_rzRmF.js} +1 -1
  71. package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-CUNzD2kH.js} +1 -1
  72. package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-Ca3UFXZe.js} +1 -1
  73. package/lib/public/assets/{zap-4yN8Jdfw.js → zap-B2bH2vKX.js} +1 -1
  74. package/lib/public/index.html +1 -1
  75. package/lib/src/InternalHttpClient.js +5 -1
  76. package/lib/src/XenonCapabilityManager.js +15 -1
  77. package/lib/src/app/appiumBasePath.js +32 -0
  78. package/lib/src/app/index.js +12 -0
  79. package/lib/src/app/insertBeforeRoutes.js +24 -4
  80. package/lib/src/app/registerCommandAuth.js +152 -33
  81. package/lib/src/app/routers/control.js +11 -104
  82. package/lib/src/app/routers/grid.js +28 -8
  83. package/lib/src/app/routers/nodePhoneControl.js +301 -0
  84. package/lib/src/app/ws/h264StreamWs.js +43 -12
  85. package/lib/src/app/ws/logcatWs.js +48 -14
  86. package/lib/src/app/ws/nodeSocketRelay.js +167 -0
  87. package/lib/src/app/ws/upgradeRouter.js +215 -0
  88. package/lib/src/dashboard/event-manager.js +7 -6
  89. package/lib/src/data-service/device-service.js +152 -49
  90. package/lib/src/data-service/device-store.js +92 -10
  91. package/lib/src/data-service/deviceClaims.js +116 -0
  92. package/lib/src/data-service/deviceFieldOwners.js +32 -10
  93. package/lib/src/data-service/prisma-store.js +102 -31
  94. package/lib/src/device-managers/AndroidDeviceManager.js +40 -42
  95. package/lib/src/device-managers/HealthMonitorService.js +17 -2
  96. package/lib/src/device-managers/IOSDeviceManager.js +5 -1
  97. package/lib/src/device-managers/NodeDevices.js +7 -2
  98. package/lib/src/device-managers/android/AndroidH264StreamService.js +13 -5
  99. package/lib/src/device-managers/android/AndroidStreamService.js +9 -6
  100. package/lib/src/device-managers/android/previewHold.js +2 -2
  101. package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
  102. package/lib/src/device-managers/ios/IOSStreamService.js +162 -40
  103. package/lib/src/device-managers/localDeviceHosts.js +86 -0
  104. package/lib/src/device-managers/ownDeviceRow.js +38 -0
  105. package/lib/src/device-utils.js +78 -24
  106. package/lib/src/gateway/createHandoff.js +73 -0
  107. package/lib/src/gateway/defaultGateway.js +80 -0
  108. package/lib/src/gateway/forwardToNode.js +147 -0
  109. package/lib/src/gateway/hubSessionToken.js +356 -0
  110. package/lib/src/gateway/internalCall.js +90 -0
  111. package/lib/src/gateway/nodeSessionStatus.js +140 -0
  112. package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
  113. package/lib/src/gateway/sessionCreate.js +136 -0
  114. package/lib/src/gateway/sessionGateway.js +201 -0
  115. package/lib/src/gateway/sessionLocator.js +100 -0
  116. package/lib/src/generated/client/edge.js +7 -4
  117. package/lib/src/generated/client/index-browser.js +4 -1
  118. package/lib/src/generated/client/index.d.ts +112 -1
  119. package/lib/src/generated/client/index.js +7 -4
  120. package/lib/src/generated/client/package.json +1 -1
  121. package/lib/src/generated/client/schema.prisma +9 -0
  122. package/lib/src/generated/client/wasm.js +4 -1
  123. package/lib/src/helpers/index.js +5 -1
  124. package/lib/src/logger.js +5 -0
  125. package/lib/src/middleware/authMiddleware.js +80 -0
  126. package/lib/src/middleware/commandAuth.js +31 -17
  127. package/lib/src/middleware/csrfMiddleware.js +3 -2
  128. package/lib/src/middleware/deviceAccessGuard.js +4 -1
  129. package/lib/src/middleware/sessionListingFilter.js +153 -0
  130. package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
  131. package/lib/src/plugin.js +27 -1
  132. package/lib/src/prisma.js +2 -1
  133. package/lib/src/services/DeviceReconciler.js +5 -2
  134. package/lib/src/services/OrphanSweeper.js +12 -19
  135. package/lib/src/services/PortAllocator.js +75 -16
  136. package/lib/src/services/ServerManager.js +63 -30
  137. package/lib/src/services/SessionLifecycleService.js +335 -76
  138. package/lib/src/services/ShutdownCoordinator.js +7 -1
  139. package/lib/src/services/SocketServer.js +11 -2
  140. package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
  141. package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
  142. package/lib/src/services/device-access/ticketActorAccess.js +3 -1
  143. package/lib/src/services/lease/LeaseOrphanSweeper.js +34 -6
  144. package/lib/src/services/lease/LeaseService.js +19 -5
  145. package/lib/src/services/session/nodeCreateCaps.js +55 -0
  146. package/lib/src/services/session/sessionCredentials.js +7 -59
  147. package/lib/src/sessions/CloudSession.js +12 -0
  148. package/lib/src/sessions/LocalSession.js +52 -26
  149. package/lib/src/sessions/RemoteSession.js +116 -9
  150. package/lib/src/sessions/SessionManager.js +49 -6
  151. package/lib/src/sessions/appiumUmbrella.js +32 -0
  152. package/lib/test/helpers/appium-umbrella.js +84 -0
  153. package/lib/test/helpers/fake-device-table.js +97 -0
  154. package/lib/test/helpers/loki-stores.js +41 -0
  155. package/lib/test/helpers/loopbackRequest.js +77 -0
  156. package/lib/test/helpers/own-node-id.js +29 -0
  157. package/lib/test/helpers/scratch-database.js +100 -0
  158. package/lib/test/helpers/scratch-port-leases.js +119 -0
  159. package/lib/test/helpers/test-container.js +35 -27
  160. package/lib/test/integration/androidDevices.spec.js +5 -4
  161. package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
  162. package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
  163. package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
  164. package/lib/test/setup/scratch-default-database.js +32 -0
  165. package/lib/test/unit/AndroidDeviceManager.spec.js +56 -1
  166. package/lib/test/unit/DeviceModel.spec.js +9 -9
  167. package/lib/test/unit/HealingCascade.test.js +5 -1
  168. package/lib/test/unit/OrphanSweeper.test.js +17 -13
  169. package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
  170. package/lib/test/unit/StreamTicketService.spec.js +4 -1
  171. package/lib/test/unit/TokenEndpoint.spec.js +17 -3
  172. package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
  173. package/lib/test/unit/android-h264-service.spec.js +24 -0
  174. package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
  175. package/lib/test/unit/apps-team-visibility.spec.js +16 -16
  176. package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
  177. package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
  178. package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
  179. package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
  180. package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
  181. package/lib/test/unit/authMiddleware.test.js +3 -0
  182. package/lib/test/unit/bug-report/route.spec.js +9 -9
  183. package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
  184. package/lib/test/unit/clipboard-route.spec.js +22 -8
  185. package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
  186. package/lib/test/unit/command-auth.spec.js +31 -31
  187. package/lib/test/unit/control-device-lookup.spec.js +9 -9
  188. package/lib/test/unit/control-forward-decision.spec.js +115 -0
  189. package/lib/test/unit/control-lookup-failure.spec.js +3 -3
  190. package/lib/test/unit/create-error-message.spec.js +43 -0
  191. package/lib/test/unit/create-handoff.spec.js +86 -0
  192. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  193. package/lib/test/unit/device-access-guard.spec.js +66 -28
  194. package/lib/test/unit/device-service.spec.js +4 -0
  195. package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
  196. package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
  197. package/lib/test/unit/device-team-guard.spec.js +20 -20
  198. package/lib/test/unit/device-utils.spec.js +8 -0
  199. package/lib/test/unit/extract-team-cap.spec.js +65 -0
  200. package/lib/test/unit/grid-admin-only.spec.js +3 -3
  201. package/lib/test/unit/healing-tier-gate.spec.js +5 -1
  202. package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
  203. package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
  204. package/lib/test/unit/helpers.spec.js +27 -0
  205. package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
  206. package/lib/test/unit/hub-create-once.spec.js +190 -0
  207. package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
  208. package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
  209. package/lib/test/unit/hub-node-control-forward.spec.js +608 -0
  210. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
  211. package/lib/test/unit/hub-node-race.spec.js +426 -0
  212. package/lib/test/unit/hub-node-same-host.spec.js +356 -0
  213. package/lib/test/unit/hub-register-removal.spec.js +165 -0
  214. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
  215. package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
  216. package/lib/test/unit/hub-session-token.spec.js +265 -0
  217. package/lib/test/unit/insert-before-routes.spec.js +5 -5
  218. package/lib/test/unit/install-repository-app-team.spec.js +6 -2
  219. package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
  220. package/lib/test/unit/internal-call.spec.js +181 -0
  221. package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
  222. package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
  223. package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
  224. package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
  225. package/lib/test/unit/is-xenon-running.spec.js +69 -0
  226. package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
  227. package/lib/test/unit/lease/LeaseService.spec.js +28 -1
  228. package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
  229. package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +107 -0
  230. package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
  231. package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
  232. package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
  233. package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
  234. package/lib/test/unit/logcat-ws.spec.js +12 -1
  235. package/lib/test/unit/loopback-request.spec.js +81 -0
  236. package/lib/test/unit/mark-node-busy-loki.spec.js +53 -0
  237. package/lib/test/unit/node-create-caps.spec.js +54 -0
  238. package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
  239. package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
  240. package/lib/test/unit/node-phone-control.spec.js +131 -0
  241. package/lib/test/unit/node-socket-relay.spec.js +357 -0
  242. package/lib/test/unit/omni-vision.spec.js +7 -3
  243. package/lib/test/unit/plugin.spec.js +45 -4
  244. package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
  245. package/lib/test/unit/profile-router.test.js +11 -11
  246. package/lib/test/unit/queue-requester.spec.js +5 -0
  247. package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
  248. package/lib/test/unit/queue-visibility.spec.js +1 -1
  249. package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
  250. package/lib/test/unit/recording-instrumentation.spec.js +5 -0
  251. package/lib/test/unit/recording-source-ended.spec.js +5 -0
  252. package/lib/test/unit/recording-store.spec.js +9 -3
  253. package/lib/test/unit/recordings-library-routes.spec.js +51 -51
  254. package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
  255. package/lib/test/unit/remote-session-create.spec.js +130 -0
  256. package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
  257. package/lib/test/unit/reset-link.test.js +14 -14
  258. package/lib/test/unit/session-app-resolution.spec.js +14 -10
  259. package/lib/test/unit/session-assets.spec.js +9 -9
  260. package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
  261. package/lib/test/unit/session-create-layer.spec.js +255 -0
  262. package/lib/test/unit/session-credential-strip.spec.js +25 -9
  263. package/lib/test/unit/session-credentials.spec.js +0 -47
  264. package/lib/test/unit/session-listing-auth.spec.js +345 -0
  265. package/lib/test/unit/session-locator.spec.js +250 -0
  266. package/lib/test/unit/session-node-calls.spec.js +174 -0
  267. package/lib/test/unit/session-owner-resolver.spec.js +107 -0
  268. package/lib/test/unit/session-recovery-routing.spec.js +118 -0
  269. package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
  270. package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
  271. package/lib/test/unit/socketServer.bearer.spec.js +3 -1
  272. package/lib/test/unit/stream-leave-route.spec.js +19 -7
  273. package/lib/test/unit/stream-own-device-row.spec.js +164 -0
  274. package/lib/test/unit/stream-stop-ownership.spec.js +17 -5
  275. package/lib/test/unit/stream-stop-recording.spec.js +17 -5
  276. package/lib/test/unit/stream-ticket-identity.spec.js +26 -6
  277. package/lib/test/unit/stream-watchdog-timers.spec.js +46 -0
  278. package/lib/test/unit/upgrade-router.spec.js +446 -0
  279. package/lib/test/unit/users-router.test.js +11 -8
  280. package/lib/test/unit/verify-credential.spec.js +4 -1
  281. package/lib/test/unit/webdriver-info-route.spec.js +64 -0
  282. package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
  283. package/lib/tsconfig.tsbuildinfo +1 -1
  284. package/package.json +3 -4
  285. package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
  286. package/prisma/schema.prisma +9 -0
  287. package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
  288. package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
  289. package/lib/src/proxy/wd-command-proxy.js +0 -221
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ require("reflect-metadata");
16
+ const chai_1 = require("chai");
17
+ const net_1 = __importDefault(require("net"));
18
+ const sinon_1 = __importDefault(require("sinon"));
19
+ const PortAllocator_1 = require("../../src/services/PortAllocator");
20
+ const scratch_port_leases_1 = require("../helpers/scratch-port-leases");
21
+ const SHAPES = [
22
+ { host: '127.0.0.1', label: 'IPv4 loopback (127.0.0.1)' },
23
+ { host: '::1', label: 'IPv6 loopback (::1)' },
24
+ { host: '0.0.0.0', label: 'IPv4 wildcard (0.0.0.0)' },
25
+ { host: '::', label: 'IPv6 wildcard, dual-stack (::) — what iproxy does' },
26
+ { host: '::', ipv6Only: true, label: 'IPv6 wildcard, v6-only (::)' },
27
+ ];
28
+ function listen(port, host, ipv6Only = false) {
29
+ return new Promise((resolve, reject) => {
30
+ const server = net_1.default.createServer((socket) => socket.destroy());
31
+ server.once('error', reject);
32
+ server.listen({ port, host, ipv6Only }, () => resolve(server));
33
+ });
34
+ }
35
+ function close(server) {
36
+ return new Promise((resolve) => server.close(() => resolve()));
37
+ }
38
+ describe('PortAllocator OS probe and lease ownership (real sockets, scratch DB)', () => {
39
+ const scratch = (0, scratch_port_leases_1.useScratchPortLeases)();
40
+ let allocator;
41
+ let servers;
42
+ beforeEach(() => {
43
+ allocator = new PortAllocator_1.PortAllocator();
44
+ allocator.configure({
45
+ mjpeg: [scratch.base, scratch.base + 1],
46
+ wda: [scratch.base + 50, scratch.base + 51],
47
+ });
48
+ servers = [];
49
+ });
50
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
51
+ yield Promise.all(servers.map(close));
52
+ sinon_1.default.restore();
53
+ }));
54
+ function occupy(port, shape) {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ servers.push(yield listen(port, shape.host, shape.ipv6Only));
57
+ });
58
+ }
59
+ function leaseOf(port) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ return scratch.db.portLease.findUnique({ where: { port } });
62
+ });
63
+ }
64
+ describe('acquire skips a port something is listening on', () => {
65
+ for (const shape of SHAPES) {
66
+ it(`a listener on ${shape.label}`, () => __awaiter(void 0, void 0, void 0, function* () {
67
+ var _a;
68
+ yield occupy(scratch.base, shape);
69
+ const port = yield allocator.acquire('mjpeg', 'android-s9');
70
+ (0, chai_1.expect)(port, `must not lease a port with a listener on ${shape.host}`).to.equal(scratch.base + 1);
71
+ (0, chai_1.expect)(yield leaseOf(scratch.base)).to.equal(null);
72
+ (0, chai_1.expect)((_a = (yield leaseOf(scratch.base + 1))) === null || _a === void 0 ? void 0 : _a.leasedToUdid).to.equal('android-s9');
73
+ }));
74
+ }
75
+ it("drops the udid's own stale lease when an iproxy-shaped listener holds the port", () => __awaiter(void 0, void 0, void 0, function* () {
76
+ // The reuse path: the S9+ still has a lease row on the port while the
77
+ // iPhone's iproxy is the one listening there.
78
+ const now = Date.now();
79
+ yield scratch.db.portLease.create({
80
+ data: {
81
+ port: scratch.base,
82
+ purpose: 'mjpeg',
83
+ leasedToUdid: 'android-s9',
84
+ leasedAt: now,
85
+ expiresAt: now + 60000,
86
+ },
87
+ });
88
+ yield occupy(scratch.base, SHAPES[3]);
89
+ const port = yield allocator.acquire('mjpeg', 'android-s9');
90
+ (0, chai_1.expect)(port).to.equal(scratch.base + 1);
91
+ (0, chai_1.expect)(yield leaseOf(scratch.base)).to.equal(null);
92
+ }));
93
+ });
94
+ it('still leases a port nothing is listening on', () => __awaiter(void 0, void 0, void 0, function* () {
95
+ const port = yield allocator.acquire('mjpeg', 'android-s9');
96
+ (0, chai_1.expect)(port).to.equal(scratch.base);
97
+ const row = yield leaseOf(scratch.base);
98
+ (0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.leasedToUdid).to.equal('android-s9');
99
+ (0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.purpose).to.equal('mjpeg');
100
+ }));
101
+ describe('a host without IPv6', () => {
102
+ it('does not count an IPv6 address it cannot bind as "in use"', () => __awaiter(void 0, void 0, void 0, function* () {
103
+ const probe = sinon_1.default.stub(allocator, 'bindProbe').callsFake((...args) => __awaiter(void 0, void 0, void 0, function* () {
104
+ const host = args[1];
105
+ if (host === '::')
106
+ return 'EAFNOSUPPORT';
107
+ if (host === '::1')
108
+ return 'EADDRNOTAVAIL';
109
+ return null;
110
+ }));
111
+ (0, chai_1.expect)(yield allocator.acquire('mjpeg', 'android-s9')).to.equal(scratch.base);
112
+ const hosts = probe.getCalls().map((c) => c.args[1]);
113
+ (0, chai_1.expect)(hosts).to.include.members(['127.0.0.1', '0.0.0.0', '::1', '::']);
114
+ }));
115
+ it('still counts EADDRINUSE on an IPv6 address as in use', () => __awaiter(void 0, void 0, void 0, function* () {
116
+ sinon_1.default.stub(allocator, 'bindProbe').callsFake((...args) => __awaiter(void 0, void 0, void 0, function* () {
117
+ const [port, host] = args;
118
+ return host === '::' && port === scratch.base ? 'EADDRINUSE' : null;
119
+ }));
120
+ (0, chai_1.expect)(yield allocator.acquire('mjpeg', 'android-s9')).to.equal(scratch.base + 1);
121
+ }));
122
+ });
123
+ describe('release is scoped to the lease holder', () => {
124
+ it("leaves another udid's lease on the same port alone", () => __awaiter(void 0, void 0, void 0, function* () {
125
+ var _a;
126
+ const port = yield allocator.acquire('mjpeg', 'android-s9');
127
+ yield allocator.release(port, 'iphone-17');
128
+ (0, chai_1.expect)((_a = (yield leaseOf(port))) === null || _a === void 0 ? void 0 : _a.leasedToUdid, 'a stale owner must not free a live lease').to.equal('android-s9');
129
+ }));
130
+ it('deletes the lease when the holder releases it', () => __awaiter(void 0, void 0, void 0, function* () {
131
+ const port = yield allocator.acquire('mjpeg', 'android-s9');
132
+ yield allocator.release(port, 'android-s9');
133
+ (0, chai_1.expect)(yield leaseOf(port)).to.equal(null);
134
+ }));
135
+ });
136
+ describe('claim: leasing a port the udid is already serving on', () => {
137
+ it('leases a port with a live listener, which acquire would refuse', () => __awaiter(void 0, void 0, void 0, function* () {
138
+ yield occupy(scratch.base + 50, SHAPES[3]);
139
+ (0, chai_1.expect)(yield allocator.claim('wda', 'iphone-17', scratch.base + 50)).to.equal(true);
140
+ const row = yield leaseOf(scratch.base + 50);
141
+ (0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.leasedToUdid).to.equal('iphone-17');
142
+ (0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.purpose).to.equal('wda');
143
+ }));
144
+ it("refuses, and keeps the row, when another udid holds the port's lease", () => __awaiter(void 0, void 0, void 0, function* () {
145
+ var _a;
146
+ const held = yield allocator.acquire('wda', 'iphone-other');
147
+ (0, chai_1.expect)(yield allocator.claim('wda', 'iphone-17', held)).to.equal(false);
148
+ (0, chai_1.expect)((_a = (yield leaseOf(held))) === null || _a === void 0 ? void 0 : _a.leasedToUdid).to.equal('iphone-other');
149
+ }));
150
+ });
151
+ });
@@ -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 profile_1 = require("../../src/app/routers/profile");
21
21
  const typedi_1 = require("typedi");
22
22
  const ApiKeyService_1 = require("../../src/services/ApiKeyService");
@@ -34,13 +34,13 @@ describe('profile router', () => {
34
34
  it('GET /profile/access-key returns the caller accessKey', () => __awaiter(void 0, void 0, void 0, function* () {
35
35
  sinon_1.default.stub(typedi_1.Container.get(UserService_1.UserService), 'findById').resolves({ accessKey: 'xen_abc' });
36
36
  const app = appWithAuth({ userId: 'u1', role: 'ADMIN', scopes: 'devices,sessions,read' });
37
- const r = yield (0, supertest_1.default)(app).get('/profile/access-key');
37
+ const r = yield (0, loopbackRequest_1.default)(app).get('/profile/access-key');
38
38
  (0, chai_1.expect)(r.body.accessKey).to.equal('xen_abc');
39
39
  }));
40
40
  it('POST /profile/access-key/rotate returns a new accessKey', () => __awaiter(void 0, void 0, void 0, function* () {
41
41
  sinon_1.default.stub(typedi_1.Container.get(UserService_1.UserService), 'rotateAccessKey').resolves({ id: 'u1', accessKey: 'xen_NEW' });
42
42
  const app = appWithAuth({ userId: 'u1', role: 'ADMIN', scopes: 'devices,sessions,read' });
43
- const r = yield (0, supertest_1.default)(app).post('/profile/access-key/rotate');
43
+ const r = yield (0, loopbackRequest_1.default)(app).post('/profile/access-key/rotate');
44
44
  (0, chai_1.expect)(r.body.accessKey).to.equal('xen_NEW');
45
45
  }));
46
46
  it('GET /profile/tokens lists caller tokens without secrets', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -48,7 +48,7 @@ describe('profile router', () => {
48
48
  { id: 'k1', name: 'CI', scopes: 'sessions,read', expiresAt: null, createdAt: new Date(), lastUsedAt: null },
49
49
  ]);
50
50
  const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
51
- const r = yield (0, supertest_1.default)(app).get('/profile/tokens');
51
+ const r = yield (0, loopbackRequest_1.default)(app).get('/profile/tokens');
52
52
  (0, chai_1.expect)(r.body[0].name).to.equal('CI');
53
53
  (0, chai_1.expect)(r.body[0].keyHash).to.be.undefined;
54
54
  }));
@@ -56,14 +56,14 @@ describe('profile router', () => {
56
56
  const create = sinon_1.default.stub(typedi_1.Container.get(ApiKeyService_1.ApiKeyService), 'create')
57
57
  .resolves({ id: 'k2', raw: 'rawsecret' });
58
58
  const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
59
- const r = yield (0, supertest_1.default)(app).post('/profile/tokens').send({ name: 'CI' });
59
+ const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI' });
60
60
  (0, chai_1.expect)(r.status).to.equal(201);
61
61
  (0, chai_1.expect)(r.body.token).to.equal('rawsecret');
62
62
  (0, chai_1.expect)(create.firstCall.args[0].scopes).to.deep.equal(['sessions', 'read']);
63
63
  }));
64
64
  it('POST /profile/tokens rejects an attempt to widen scopes', () => __awaiter(void 0, void 0, void 0, function* () {
65
65
  const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
66
- const r = yield (0, supertest_1.default)(app).post('/profile/tokens').send({ name: 'CI', scopes: ['admin'] });
66
+ const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI', scopes: ['admin'] });
67
67
  (0, chai_1.expect)(r.status).to.equal(400);
68
68
  (0, chai_1.expect)(r.body.error).to.match(/cannot widen/);
69
69
  }));
@@ -73,7 +73,7 @@ describe('profile router', () => {
73
73
  .resolves({ id: 'k3', raw: 'rawsecret' });
74
74
  const future = new Date(Date.now() + 30 * 24 * 60 * 60000).toISOString();
75
75
  const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
76
- const r = yield (0, supertest_1.default)(app).post('/profile/tokens').send({ name: 'CI', expiresAt: future });
76
+ const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI', expiresAt: future });
77
77
  (0, chai_1.expect)(r.status).to.equal(201);
78
78
  (0, chai_1.expect)(r.body.expiresAt).to.equal(future);
79
79
  (0, chai_1.expect)((_a = create.firstCall.args[0].expiresAt) === null || _a === void 0 ? void 0 : _a.toISOString()).to.equal(future);
@@ -81,13 +81,13 @@ describe('profile router', () => {
81
81
  it('POST /profile/tokens rejects a past expiresAt with 400', () => __awaiter(void 0, void 0, void 0, function* () {
82
82
  const past = new Date(Date.now() - 60000).toISOString();
83
83
  const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
84
- const r = yield (0, supertest_1.default)(app).post('/profile/tokens').send({ name: 'CI', expiresAt: past });
84
+ const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI', expiresAt: past });
85
85
  (0, chai_1.expect)(r.status).to.equal(400);
86
86
  (0, chai_1.expect)(r.body.error).to.match(/future/);
87
87
  }));
88
88
  it('POST /profile/tokens rejects an unparseable expiresAt with 400', () => __awaiter(void 0, void 0, void 0, function* () {
89
89
  const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
90
- const r = yield (0, supertest_1.default)(app).post('/profile/tokens').send({ name: 'CI', expiresAt: 'not-a-date' });
90
+ const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI', expiresAt: 'not-a-date' });
91
91
  (0, chai_1.expect)(r.status).to.equal(400);
92
92
  (0, chai_1.expect)(r.body.error).to.match(/ISO-8601/);
93
93
  }));
@@ -95,14 +95,14 @@ describe('profile router', () => {
95
95
  sinon_1.default.stub(prisma_1.prisma.apiKey, 'findFirst').resolves({ id: 'k1', userId: 'u1' });
96
96
  const revoke = sinon_1.default.stub(typedi_1.Container.get(ApiKeyService_1.ApiKeyService), 'revoke').resolves(undefined);
97
97
  const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
98
- const r = yield (0, supertest_1.default)(app).delete('/profile/tokens/k1');
98
+ const r = yield (0, loopbackRequest_1.default)(app).delete('/profile/tokens/k1');
99
99
  (0, chai_1.expect)(r.status).to.equal(204);
100
100
  (0, chai_1.expect)(revoke.calledOnceWithExactly('k1')).to.be.true;
101
101
  }));
102
102
  it('DELETE /profile/tokens/:id 404s when the token belongs to someone else', () => __awaiter(void 0, void 0, void 0, function* () {
103
103
  sinon_1.default.stub(prisma_1.prisma.apiKey, 'findFirst').resolves(null);
104
104
  const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
105
- const r = yield (0, supertest_1.default)(app).delete('/profile/tokens/k1');
105
+ const r = yield (0, loopbackRequest_1.default)(app).delete('/profile/tokens/k1');
106
106
  (0, chai_1.expect)(r.status).to.equal(404);
107
107
  }));
108
108
  });
@@ -61,6 +61,7 @@ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
61
61
  const config_1 = require("../../src/config");
62
62
  const prisma_1 = require("../../src/prisma");
63
63
  const container_registration_1 = require("../helpers/container-registration");
64
+ const loki_stores_1 = require("../helpers/loki-stores");
64
65
  /**
65
66
  * createSession writes who asked onto the pending-session row, so the queue
66
67
  * can show a member her own and her team's requests. It goes on that row only:
@@ -79,6 +80,10 @@ const caps = (extra = {}) => ({
79
80
  });
80
81
  const withKey = (accessKey, extra = {}) => caps(Object.assign({ 'xe:options': { accessKey, token: 'tk' } }, extra));
81
82
  describe('createSession records who asked on the pending-session row', () => {
83
+ // The in-memory stores, also when this file runs on its own: without
84
+ // NODE_ENV=test the factory handed out the Prisma stores, which are the
85
+ // developer's ~/.cache/xenon/xenon.db.
86
+ (0, loki_stores_1.useLokiStores)();
82
87
  let svc;
83
88
  let device;
84
89
  let context;
@@ -15,7 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  require("reflect-metadata");
16
16
  const chai_1 = require("chai");
17
17
  const express_1 = __importDefault(require("express"));
18
- const supertest_1 = __importDefault(require("supertest"));
18
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
19
19
  const sinon_1 = __importDefault(require("sinon"));
20
20
  const grid_1 = __importDefault(require("../../src/app/routers/grid"));
21
21
  const authMiddleware_1 = require("../../src/middleware/authMiddleware");
@@ -120,24 +120,24 @@ describe('the session queue is team-scoped: own teams + a count', () => {
120
120
  afterEach(() => sinon_1.default.restore());
121
121
  describe('a member in team A', () => {
122
122
  it("GET /queue shows the request for her team's phone and her teammate's, nothing else", () => __awaiter(void 0, void 0, void 0, function* () {
123
- const r = yield (0, supertest_1.default)(alice()).get('/queue');
123
+ const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue');
124
124
  (0, chai_1.expect)(r.status).to.equal(200);
125
125
  (0, chai_1.expect)(ids(r.body)).to.deep.equal(['targets-a', 'untargeted-a']);
126
126
  (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('bob-secret-release');
127
127
  (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('bob-targeted');
128
128
  }));
129
129
  it('GET /queue never returns who asked', () => __awaiter(void 0, void 0, void 0, function* () {
130
- const r = yield (0, supertest_1.default)(alice()).get('/queue');
130
+ const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue');
131
131
  for (const shown of r.body)
132
132
  (0, chai_1.expect)(shown).to.not.have.property(REQUESTER);
133
133
  }));
134
134
  it('GET /queue/length is the whole queue, the number every caller gets', () => __awaiter(void 0, void 0, void 0, function* () {
135
- const r = yield (0, supertest_1.default)(alice()).get('/queue/length');
135
+ const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue/length');
136
136
  (0, chai_1.expect)(r.status).to.equal(200);
137
137
  (0, chai_1.expect)(r.body).to.equal(4);
138
138
  }));
139
139
  it('GET /queue/summary agrees with /queue/length, and counts what she cannot see', () => __awaiter(void 0, void 0, void 0, function* () {
140
- const r = yield (0, supertest_1.default)(alice()).get('/queue/summary');
140
+ const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue/summary');
141
141
  (0, chai_1.expect)(r.status).to.equal(200);
142
142
  (0, chai_1.expect)(r.body.total).to.equal(4);
143
143
  (0, chai_1.expect)(r.body.otherCount).to.equal(2);
@@ -145,17 +145,17 @@ describe('the session queue is team-scoped: own teams + a count', () => {
145
145
  (0, chai_1.expect)(byPlatform.reduce((n, p) => n + p.count, 0)).to.equal(4);
146
146
  }));
147
147
  it("GET /queue/status answers for another team's request exactly as for an unknown id", () => __awaiter(void 0, void 0, void 0, function* () {
148
- const unknown = yield (0, supertest_1.default)(alice()).get('/queue/status/no-such-request');
148
+ const unknown = yield (0, loopbackRequest_1.default)(alice()).get('/queue/status/no-such-request');
149
149
  (0, chai_1.expect)(unknown.status).to.equal(404);
150
150
  for (const hidden of ['targets-b', 'untargeted-b']) {
151
- const r = yield (0, supertest_1.default)(alice()).get(`/queue/status/${hidden}`);
151
+ const r = yield (0, loopbackRequest_1.default)(alice()).get(`/queue/status/${hidden}`);
152
152
  (0, chai_1.expect)(r.status, hidden).to.equal(404);
153
153
  (0, chai_1.expect)(r.body, hidden).to.deep.equal(unknown.body);
154
154
  }
155
155
  }));
156
156
  it('GET /queue/status still answers for the requests she can see', () => __awaiter(void 0, void 0, void 0, function* () {
157
157
  for (const shown of ['targets-a', 'untargeted-a']) {
158
- const r = yield (0, supertest_1.default)(alice()).get(`/queue/status/${shown}`);
158
+ const r = yield (0, loopbackRequest_1.default)(alice()).get(`/queue/status/${shown}`);
159
159
  (0, chai_1.expect)(r.status, shown).to.equal(200);
160
160
  (0, chai_1.expect)(r.body.platform, shown).to.equal('android');
161
161
  }
@@ -163,25 +163,25 @@ describe('the session queue is team-scoped: own teams + a count', () => {
163
163
  });
164
164
  describe('an admin', () => {
165
165
  it('GET /queue shows all four', () => __awaiter(void 0, void 0, void 0, function* () {
166
- const r = yield (0, supertest_1.default)(admin()).get('/queue');
166
+ const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue');
167
167
  (0, chai_1.expect)(r.status).to.equal(200);
168
168
  (0, chai_1.expect)(ids(r.body)).to.deep.equal(['targets-a', 'targets-b', 'untargeted-a', 'untargeted-b']);
169
169
  for (const shown of r.body)
170
170
  (0, chai_1.expect)(shown).to.not.have.property(REQUESTER);
171
171
  }));
172
172
  it('with no lookup of phones or teams', () => __awaiter(void 0, void 0, void 0, function* () {
173
- yield (0, supertest_1.default)(admin()).get('/queue');
173
+ yield (0, loopbackRequest_1.default)(admin()).get('/queue');
174
174
  (0, chai_1.expect)(prisma_1.prisma.device.findMany.called).to.equal(false);
175
175
  (0, chai_1.expect)(prisma_1.prisma.teamMember.findMany.called).to.equal(false);
176
176
  }));
177
177
  it('GET /queue/summary counts nothing as hidden', () => __awaiter(void 0, void 0, void 0, function* () {
178
- const r = yield (0, supertest_1.default)(admin()).get('/queue/summary');
178
+ const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue/summary');
179
179
  (0, chai_1.expect)(r.body.total).to.equal(4);
180
180
  (0, chai_1.expect)(r.body.otherCount).to.equal(0);
181
181
  }));
182
182
  it('GET /queue/length and /queue/status answer as before', () => __awaiter(void 0, void 0, void 0, function* () {
183
- (0, chai_1.expect)((yield (0, supertest_1.default)(admin()).get('/queue/length')).body).to.equal(4);
184
- const r = yield (0, supertest_1.default)(admin()).get('/queue/status/untargeted-b');
183
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(admin()).get('/queue/length')).body).to.equal(4);
184
+ const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue/status/untargeted-b');
185
185
  (0, chai_1.expect)(r.status).to.equal(200);
186
186
  }));
187
187
  });
@@ -195,13 +195,14 @@ describe('the session queue is team-scoped: own teams + a count', () => {
195
195
  ['an admin', admin],
196
196
  ]) {
197
197
  it(`are never returned to ${who}`, () => __awaiter(void 0, void 0, void 0, function* () {
198
- const r = yield (0, supertest_1.default)(caller()).get('/queue');
198
+ const r = yield (0, loopbackRequest_1.default)(caller()).get('/queue');
199
199
  const shown = r.body.find((x) => x.capability_id === 'untargeted-a');
200
200
  (0, chai_1.expect)(shown, 'the request itself is shown').to.not.equal(undefined);
201
201
  (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('amy-api-token');
202
202
  (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('amy.session.jwt');
203
- // What isn't a secret still reads as before.
204
- (0, chai_1.expect)(shown['xe:options'].accessKey).to.equal('ak_amy');
203
+ // Nor the access key: half of the pair, and it names Amy's key.
204
+ (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('ak_amy');
205
+ // What isn't a credential still reads as before.
205
206
  (0, chai_1.expect)(shown['xenon:options'].sessionName).to.equal('amy-login');
206
207
  }));
207
208
  }
@@ -102,7 +102,7 @@ describe('queue visibility', () => {
102
102
  (0, chai_1.expect)(yield seen([untargeted, targeted], alice)).to.deep.equal([false, false]);
103
103
  }));
104
104
  it('a credential narrowed to her team, whatever teams its user is in', () => __awaiter(void 0, void 0, void 0, function* () {
105
- // A team-bound key, a session token's teamId claim, or xenon:team.
105
+ // A team-bound key, a session token's teamId claim, or xe:options.team.
106
106
  (0, chai_1.expect)(yield seen([req({ userId: 'bob', teamId: 'team-a' })], alice)).to.deep.equal([true]);
107
107
  }));
108
108
  it("not a teammate's request narrowed to another team", () => __awaiter(void 0, void 0, void 0, function* () {
@@ -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 fs = __importStar(require("fs"));
54
54
  const os = __importStar(require("os"));
55
55
  const path = __importStar(require("path"));
@@ -104,7 +104,7 @@ function binary(res, cb) {
104
104
  }
105
105
  function get(caller_1, url_1) {
106
106
  return __awaiter(this, arguments, void 0, function* (caller, url, headers = {}) {
107
- let req = (0, supertest_1.default)(buildApp(caller)).get(`/xenon/api/recordings/${GROUP}/${url}`);
107
+ let req = (0, loopbackRequest_1.default)(buildApp(caller)).get(`/xenon/api/recordings/${GROUP}/${url}`);
108
108
  for (const [k, v] of Object.entries(headers))
109
109
  req = req.set(k, v);
110
110
  const res = yield req.buffer(true).parse(binary);
@@ -21,6 +21,7 @@ const path_1 = __importDefault(require("path"));
21
21
  const api_1 = require("@opentelemetry/api");
22
22
  const attributes_1 = require("../../src/services/telemetry/attributes");
23
23
  const artifact_store_1 = require("../helpers/artifact-store");
24
+ const loki_stores_1 = require("../helpers/loki-stores");
24
25
  // Stub metrics + trace BEFORE the module loads so the module-level
25
26
  // ensureOtelInstruments() picks up our mocks. Inline import after stubbing.
26
27
  function makeMockMeter() {
@@ -56,6 +57,10 @@ function makeMockSpan() {
56
57
  };
57
58
  }
58
59
  describe('RecordingOrchestrator instrumentation', () => {
60
+ // The in-memory stores, also when this file runs on its own: without
61
+ // NODE_ENV=test the factory handed out the Prisma stores, which are the
62
+ // developer's ~/.cache/xenon/xenon.db.
63
+ (0, loki_stores_1.useLokiStores)();
59
64
  // RecordingOrchestrator resolves ARTIFACT_STORE from the container at runtime.
60
65
  (0, artifact_store_1.useArtifactStore)();
61
66
  let getMeterStub;
@@ -21,6 +21,7 @@ const path_1 = __importDefault(require("path"));
21
21
  const RecordingOrchestrator_1 = require("../../src/services/recording/RecordingOrchestrator");
22
22
  const concurrency_gate_1 = require("../../src/services/recording/concurrency-gate");
23
23
  const artifact_store_1 = require("../helpers/artifact-store");
24
+ const loki_stores_1 = require("../helpers/loki-stores");
24
25
  // Issue #203. When the ffmpeg process for a recording exits on its own nothing
25
26
  // told the orchestrator, so the row sat at RECORDING — no ended_at, no duration,
26
27
  // no size — until a human clicked Stop. Observed live during the #200 cable-pull
@@ -69,6 +70,10 @@ function makeOrch(row, overrides = {}) {
69
70
  return { orch, store, gate, videoPipeline, eventMgr, unblockDeviceFn, probeDurationMsFn };
70
71
  }
71
72
  describe('RecordingOrchestrator: ffmpeg exiting on its own', () => {
73
+ // The in-memory stores, also when this file runs on its own: without
74
+ // NODE_ENV=test the factory handed out the Prisma stores, which are the
75
+ // developer's ~/.cache/xenon/xenon.db.
76
+ (0, loki_stores_1.useLokiStores)();
72
77
  // compositeOutputPath and per-device paths resolve through ArtifactStore.
73
78
  (0, artifact_store_1.useArtifactStore)();
74
79
  afterEach(() => sinon_1.default.restore());
@@ -13,12 +13,18 @@ require("reflect-metadata");
13
13
  const chai_1 = require("chai");
14
14
  const recording_store_1 = require("../../src/services/recording/recording-store");
15
15
  const prisma_1 = require("../../src/prisma");
16
+ const scratch_database_1 = require("../helpers/scratch-database");
16
17
  describe('RecordingStore (Prisma round-trip)', () => {
18
+ // Its own migrated database: the round trip wrote these rows to the
19
+ // developer's ~/.cache/xenon/xenon.db, and failed where it lacked the tables.
20
+ const scratch = (0, scratch_database_1.useScratchDatabase)();
17
21
  const store = new recording_store_1.RecordingStore();
22
+ // Through the scratch client itself: the helper's own afterEach, which
23
+ // runs first, has already put `prisma` back on the server's database.
18
24
  afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
19
- yield prisma_1.prisma.annotation.deleteMany({ where: { recording_id: { contains: 'test-' } } });
20
- yield prisma_1.prisma.bookmark.deleteMany({ where: { recording_id: { contains: 'test-' } } });
21
- yield prisma_1.prisma.recording.deleteMany({ where: { group_id: { startsWith: 'test-' } } });
25
+ yield scratch.db.annotation.deleteMany({ where: { recording_id: { contains: 'test-' } } });
26
+ yield scratch.db.bookmark.deleteMany({ where: { recording_id: { contains: 'test-' } } });
27
+ yield scratch.db.recording.deleteMany({ where: { group_id: { startsWith: 'test-' } } });
22
28
  }));
23
29
  it('creates a recording row with status=RECORDING', () => __awaiter(void 0, void 0, void 0, function* () {
24
30
  const rec = yield store.create({