@xenon-device-management/xenon 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/README.md +2 -1
  2. package/lib/package.json +3 -4
  3. package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-XpewX5nn.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-BQDv3ZCC.js} +1 -1
  5. package/lib/public/assets/BugReportButton-C7OmS61B.js +10 -0
  6. package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-wl3l2Zlt.js} +1 -1
  7. package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-fPxujqqE.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-5bCutWrs.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-owsBiDT1.js} +1 -1
  10. package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-DFhqzPSt.js} +1 -1
  11. package/lib/public/assets/{Modal-CceoMY7D.js → Modal-Bw-a-E-e.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-WtByf5bC.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-FpWWFNgT.js} +1 -1
  14. package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-Jzn9c7o8.js} +1 -1
  15. package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-BR7nOhpT.js} +1 -1
  16. package/lib/public/assets/{Table-LV7zFyZg.js → Table-B_Va3V05.js} +1 -1
  17. package/lib/public/assets/{activity-D17TUloi.js → activity-DxRFWvkN.js} +1 -1
  18. package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-CQf-9_V6.js} +1 -1
  19. package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-SoE1Kd7N.js} +1 -1
  20. package/lib/public/assets/{apps-Dbnzd0fW.js → apps-CgGXTlgn.js} +1 -1
  21. package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-uQ6WIwiC.js} +1 -1
  22. package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-C9RyfzXS.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-DFHi_8yG.js} +1 -1
  24. package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-Bii1t4qc.js} +2 -2
  25. package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-Bnv8Z9hi.js} +1 -1
  26. package/lib/public/assets/{button-xtAhYR-f.js → button-_Y78wXiP.js} +1 -1
  27. package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-D4MqSOc2.js} +1 -1
  28. package/lib/public/assets/{check-CyuEoKkd.js → check-uGHCXe-L.js} +1 -1
  29. package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-Dy1OqpzB.js} +1 -1
  30. package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-BUsldEli.js} +1 -1
  31. package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-D3Qq0tKp.js} +1 -1
  32. package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-B4IOD1DE.js} +1 -1
  33. package/lib/public/assets/{clock-Cd-t7xpe.js → clock-BfYfzvSg.js} +1 -1
  34. package/lib/public/assets/{copy-C6zerX3I.js → copy-DZwYE5Va.js} +1 -1
  35. package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CRG4Utt5.js} +3 -3
  36. package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
  37. package/lib/public/assets/{download-x9WP7mcL.js → download-CCDYo_TN.js} +1 -1
  38. package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-DuLCmVFb.js} +1 -1
  39. package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-CtKNPqzb.js} +1 -1
  40. package/lib/public/assets/{index-BR1wTIPC.js → index-3uxl3o7K.js} +43 -43
  41. package/lib/public/assets/{index-BvH_ekRy.js → index-Zy1Op5C1.js} +1 -1
  42. package/lib/public/assets/{input-DejcCiTI.js → input-CzTWvUXZ.js} +1 -1
  43. package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CI-nYDFM.js} +1 -1
  44. package/lib/public/assets/{layers-BKqYLiHE.js → layers-YZZGFyTX.js} +1 -1
  45. package/lib/public/assets/{lock-LFsFhc17.js → lock-L8tzWgDo.js} +1 -1
  46. package/lib/public/assets/{login-x2pkzt-t.js → login-DBbX-fu8.js} +1 -1
  47. package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-AJwkOIsE.js} +1 -1
  48. package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-Di_66F2m.js} +1 -1
  49. package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-Cnab2zdg.js} +1 -1
  50. package/lib/public/assets/{network-Z_zBhpsD.js → network-DiSumY-1.js} +1 -1
  51. package/lib/public/assets/{overview-BY5uv08S.js → overview-CwE4gXl5.js} +1 -1
  52. package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CfR4Sm9V.js} +1 -1
  53. package/lib/public/assets/{plus-BTD0dnQ0.js → plus-DOS8P9CQ.js} +1 -1
  54. package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-BYpHwQIH.js} +1 -1
  55. package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BRVJU_qV.js} +1 -1
  56. package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Bi3JvAFZ.js} +1 -1
  57. package/lib/public/assets/{select-Uc9E5pye.js → select-Cqx7pEou.js} +1 -1
  58. package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C0DsncKj.js} +1 -1
  59. package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CAhpdilF.js} +1 -1
  60. package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CrCVeW2m.js} +1 -1
  61. package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-F_hwYqEt.js} +1 -1
  62. package/lib/public/assets/{settings-CkSWiZq8.js → settings-C-s5pypL.js} +1 -1
  63. package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-DY5uzv6A.js} +1 -1
  64. package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-Dli7gwyg.js} +1 -1
  65. package/lib/public/assets/{teams-PpoEQz31.js → teams-ui4IhHNC.js} +1 -1
  66. package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV4hSmXJ.js} +1 -1
  67. package/lib/public/assets/{upload-C3i7sAZl.js → upload-DkGy_J-L.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-BFtMqQOh.js} +1 -1
  69. package/lib/public/assets/{users-Drd7ifJx.js → users-BU0nXIVe.js} +1 -1
  70. package/lib/public/assets/{users-CEJ3fC78.js → users-Nb_rzRmF.js} +1 -1
  71. package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-CUNzD2kH.js} +1 -1
  72. package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-Ca3UFXZe.js} +1 -1
  73. package/lib/public/assets/{zap-4yN8Jdfw.js → zap-B2bH2vKX.js} +1 -1
  74. package/lib/public/index.html +1 -1
  75. package/lib/src/InternalHttpClient.js +5 -1
  76. package/lib/src/XenonCapabilityManager.js +15 -1
  77. package/lib/src/app/appiumBasePath.js +32 -0
  78. package/lib/src/app/index.js +12 -0
  79. package/lib/src/app/insertBeforeRoutes.js +24 -4
  80. package/lib/src/app/registerCommandAuth.js +152 -33
  81. package/lib/src/app/routers/control.js +11 -104
  82. package/lib/src/app/routers/grid.js +28 -8
  83. package/lib/src/app/routers/nodePhoneControl.js +301 -0
  84. package/lib/src/app/ws/h264StreamWs.js +43 -12
  85. package/lib/src/app/ws/logcatWs.js +48 -14
  86. package/lib/src/app/ws/nodeSocketRelay.js +167 -0
  87. package/lib/src/app/ws/upgradeRouter.js +215 -0
  88. package/lib/src/dashboard/event-manager.js +7 -6
  89. package/lib/src/data-service/device-service.js +152 -49
  90. package/lib/src/data-service/device-store.js +92 -10
  91. package/lib/src/data-service/deviceClaims.js +116 -0
  92. package/lib/src/data-service/deviceFieldOwners.js +32 -10
  93. package/lib/src/data-service/prisma-store.js +102 -31
  94. package/lib/src/device-managers/AndroidDeviceManager.js +40 -42
  95. package/lib/src/device-managers/HealthMonitorService.js +17 -2
  96. package/lib/src/device-managers/IOSDeviceManager.js +5 -1
  97. package/lib/src/device-managers/NodeDevices.js +7 -2
  98. package/lib/src/device-managers/android/AndroidH264StreamService.js +13 -5
  99. package/lib/src/device-managers/android/AndroidStreamService.js +9 -6
  100. package/lib/src/device-managers/android/previewHold.js +2 -2
  101. package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
  102. package/lib/src/device-managers/ios/IOSStreamService.js +162 -40
  103. package/lib/src/device-managers/localDeviceHosts.js +86 -0
  104. package/lib/src/device-managers/ownDeviceRow.js +38 -0
  105. package/lib/src/device-utils.js +78 -24
  106. package/lib/src/gateway/createHandoff.js +73 -0
  107. package/lib/src/gateway/defaultGateway.js +80 -0
  108. package/lib/src/gateway/forwardToNode.js +147 -0
  109. package/lib/src/gateway/hubSessionToken.js +356 -0
  110. package/lib/src/gateway/internalCall.js +90 -0
  111. package/lib/src/gateway/nodeSessionStatus.js +140 -0
  112. package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
  113. package/lib/src/gateway/sessionCreate.js +136 -0
  114. package/lib/src/gateway/sessionGateway.js +201 -0
  115. package/lib/src/gateway/sessionLocator.js +100 -0
  116. package/lib/src/generated/client/edge.js +7 -4
  117. package/lib/src/generated/client/index-browser.js +4 -1
  118. package/lib/src/generated/client/index.d.ts +112 -1
  119. package/lib/src/generated/client/index.js +7 -4
  120. package/lib/src/generated/client/package.json +1 -1
  121. package/lib/src/generated/client/schema.prisma +9 -0
  122. package/lib/src/generated/client/wasm.js +4 -1
  123. package/lib/src/helpers/index.js +5 -1
  124. package/lib/src/logger.js +5 -0
  125. package/lib/src/middleware/authMiddleware.js +80 -0
  126. package/lib/src/middleware/commandAuth.js +31 -17
  127. package/lib/src/middleware/csrfMiddleware.js +3 -2
  128. package/lib/src/middleware/deviceAccessGuard.js +4 -1
  129. package/lib/src/middleware/sessionListingFilter.js +153 -0
  130. package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
  131. package/lib/src/plugin.js +27 -1
  132. package/lib/src/prisma.js +2 -1
  133. package/lib/src/services/DeviceReconciler.js +5 -2
  134. package/lib/src/services/OrphanSweeper.js +12 -19
  135. package/lib/src/services/PortAllocator.js +75 -16
  136. package/lib/src/services/ServerManager.js +63 -30
  137. package/lib/src/services/SessionLifecycleService.js +335 -76
  138. package/lib/src/services/ShutdownCoordinator.js +7 -1
  139. package/lib/src/services/SocketServer.js +11 -2
  140. package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
  141. package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
  142. package/lib/src/services/device-access/ticketActorAccess.js +3 -1
  143. package/lib/src/services/lease/LeaseOrphanSweeper.js +34 -6
  144. package/lib/src/services/lease/LeaseService.js +19 -5
  145. package/lib/src/services/session/nodeCreateCaps.js +55 -0
  146. package/lib/src/services/session/sessionCredentials.js +7 -59
  147. package/lib/src/sessions/CloudSession.js +12 -0
  148. package/lib/src/sessions/LocalSession.js +52 -26
  149. package/lib/src/sessions/RemoteSession.js +116 -9
  150. package/lib/src/sessions/SessionManager.js +49 -6
  151. package/lib/src/sessions/appiumUmbrella.js +32 -0
  152. package/lib/test/helpers/appium-umbrella.js +84 -0
  153. package/lib/test/helpers/fake-device-table.js +97 -0
  154. package/lib/test/helpers/loki-stores.js +41 -0
  155. package/lib/test/helpers/loopbackRequest.js +77 -0
  156. package/lib/test/helpers/own-node-id.js +29 -0
  157. package/lib/test/helpers/scratch-database.js +100 -0
  158. package/lib/test/helpers/scratch-port-leases.js +119 -0
  159. package/lib/test/helpers/test-container.js +35 -27
  160. package/lib/test/integration/androidDevices.spec.js +5 -4
  161. package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
  162. package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
  163. package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
  164. package/lib/test/setup/scratch-default-database.js +32 -0
  165. package/lib/test/unit/AndroidDeviceManager.spec.js +56 -1
  166. package/lib/test/unit/DeviceModel.spec.js +9 -9
  167. package/lib/test/unit/HealingCascade.test.js +5 -1
  168. package/lib/test/unit/OrphanSweeper.test.js +17 -13
  169. package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
  170. package/lib/test/unit/StreamTicketService.spec.js +4 -1
  171. package/lib/test/unit/TokenEndpoint.spec.js +17 -3
  172. package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
  173. package/lib/test/unit/android-h264-service.spec.js +24 -0
  174. package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
  175. package/lib/test/unit/apps-team-visibility.spec.js +16 -16
  176. package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
  177. package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
  178. package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
  179. package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
  180. package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
  181. package/lib/test/unit/authMiddleware.test.js +3 -0
  182. package/lib/test/unit/bug-report/route.spec.js +9 -9
  183. package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
  184. package/lib/test/unit/clipboard-route.spec.js +22 -8
  185. package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
  186. package/lib/test/unit/command-auth.spec.js +31 -31
  187. package/lib/test/unit/control-device-lookup.spec.js +9 -9
  188. package/lib/test/unit/control-forward-decision.spec.js +115 -0
  189. package/lib/test/unit/control-lookup-failure.spec.js +3 -3
  190. package/lib/test/unit/create-error-message.spec.js +43 -0
  191. package/lib/test/unit/create-handoff.spec.js +86 -0
  192. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  193. package/lib/test/unit/device-access-guard.spec.js +66 -28
  194. package/lib/test/unit/device-service.spec.js +4 -0
  195. package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
  196. package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
  197. package/lib/test/unit/device-team-guard.spec.js +20 -20
  198. package/lib/test/unit/device-utils.spec.js +8 -0
  199. package/lib/test/unit/extract-team-cap.spec.js +65 -0
  200. package/lib/test/unit/grid-admin-only.spec.js +3 -3
  201. package/lib/test/unit/healing-tier-gate.spec.js +5 -1
  202. package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
  203. package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
  204. package/lib/test/unit/helpers.spec.js +27 -0
  205. package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
  206. package/lib/test/unit/hub-create-once.spec.js +190 -0
  207. package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
  208. package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
  209. package/lib/test/unit/hub-node-control-forward.spec.js +608 -0
  210. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
  211. package/lib/test/unit/hub-node-race.spec.js +426 -0
  212. package/lib/test/unit/hub-node-same-host.spec.js +356 -0
  213. package/lib/test/unit/hub-register-removal.spec.js +165 -0
  214. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
  215. package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
  216. package/lib/test/unit/hub-session-token.spec.js +265 -0
  217. package/lib/test/unit/insert-before-routes.spec.js +5 -5
  218. package/lib/test/unit/install-repository-app-team.spec.js +6 -2
  219. package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
  220. package/lib/test/unit/internal-call.spec.js +181 -0
  221. package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
  222. package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
  223. package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
  224. package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
  225. package/lib/test/unit/is-xenon-running.spec.js +69 -0
  226. package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
  227. package/lib/test/unit/lease/LeaseService.spec.js +28 -1
  228. package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
  229. package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +107 -0
  230. package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
  231. package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
  232. package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
  233. package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
  234. package/lib/test/unit/logcat-ws.spec.js +12 -1
  235. package/lib/test/unit/loopback-request.spec.js +81 -0
  236. package/lib/test/unit/mark-node-busy-loki.spec.js +53 -0
  237. package/lib/test/unit/node-create-caps.spec.js +54 -0
  238. package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
  239. package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
  240. package/lib/test/unit/node-phone-control.spec.js +131 -0
  241. package/lib/test/unit/node-socket-relay.spec.js +357 -0
  242. package/lib/test/unit/omni-vision.spec.js +7 -3
  243. package/lib/test/unit/plugin.spec.js +45 -4
  244. package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
  245. package/lib/test/unit/profile-router.test.js +11 -11
  246. package/lib/test/unit/queue-requester.spec.js +5 -0
  247. package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
  248. package/lib/test/unit/queue-visibility.spec.js +1 -1
  249. package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
  250. package/lib/test/unit/recording-instrumentation.spec.js +5 -0
  251. package/lib/test/unit/recording-source-ended.spec.js +5 -0
  252. package/lib/test/unit/recording-store.spec.js +9 -3
  253. package/lib/test/unit/recordings-library-routes.spec.js +51 -51
  254. package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
  255. package/lib/test/unit/remote-session-create.spec.js +130 -0
  256. package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
  257. package/lib/test/unit/reset-link.test.js +14 -14
  258. package/lib/test/unit/session-app-resolution.spec.js +14 -10
  259. package/lib/test/unit/session-assets.spec.js +9 -9
  260. package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
  261. package/lib/test/unit/session-create-layer.spec.js +255 -0
  262. package/lib/test/unit/session-credential-strip.spec.js +25 -9
  263. package/lib/test/unit/session-credentials.spec.js +0 -47
  264. package/lib/test/unit/session-listing-auth.spec.js +345 -0
  265. package/lib/test/unit/session-locator.spec.js +250 -0
  266. package/lib/test/unit/session-node-calls.spec.js +174 -0
  267. package/lib/test/unit/session-owner-resolver.spec.js +107 -0
  268. package/lib/test/unit/session-recovery-routing.spec.js +118 -0
  269. package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
  270. package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
  271. package/lib/test/unit/socketServer.bearer.spec.js +3 -1
  272. package/lib/test/unit/stream-leave-route.spec.js +19 -7
  273. package/lib/test/unit/stream-own-device-row.spec.js +164 -0
  274. package/lib/test/unit/stream-stop-ownership.spec.js +17 -5
  275. package/lib/test/unit/stream-stop-recording.spec.js +17 -5
  276. package/lib/test/unit/stream-ticket-identity.spec.js +26 -6
  277. package/lib/test/unit/stream-watchdog-timers.spec.js +46 -0
  278. package/lib/test/unit/upgrade-router.spec.js +446 -0
  279. package/lib/test/unit/users-router.test.js +11 -8
  280. package/lib/test/unit/verify-credential.spec.js +4 -1
  281. package/lib/test/unit/webdriver-info-route.spec.js +64 -0
  282. package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
  283. package/lib/tsconfig.tsbuildinfo +1 -1
  284. package/package.json +3 -4
  285. package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
  286. package/prisma/schema.prisma +9 -0
  287. package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
  288. package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
  289. package/lib/src/proxy/wd-command-proxy.js +0 -221
@@ -49,7 +49,7 @@ require("reflect-metadata");
49
49
  const chai_1 = require("chai");
50
50
  const sinon_1 = __importDefault(require("sinon"));
51
51
  const express_1 = __importDefault(require("express"));
52
- const supertest_1 = __importDefault(require("supertest"));
52
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
53
53
  const fs = __importStar(require("fs"));
54
54
  const os = __importStar(require("os"));
55
55
  const path = __importStar(require("path"));
@@ -198,7 +198,7 @@ describe('recordings library routes', () => {
198
198
  rec({ id: 'b', group_id: 'g2', started_at: new Date(T0 + 1000) }),
199
199
  rec({ id: 'c', group_id: 'g3', device_udid: 'HIDDEN', started_by: 'usr_carol' }),
200
200
  ];
201
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
201
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
202
202
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
203
203
  (0, chai_1.expect)(res.body.recordings.map((s) => s.groupId)).to.deep.equal(['g2', 'g1']);
204
204
  (0, chai_1.expect)(res.body.recordings[0].phones[0].name).to.equal('Galaxy S9+');
@@ -211,15 +211,15 @@ describe('recordings library routes', () => {
211
211
  rec({ id: 'a', group_id: 'g1' }),
212
212
  rec({ id: 'b', group_id: 'g2', device_udid: 'U2', started_at: new Date(T0 + 1000) }),
213
213
  ];
214
- const one = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings?limit=1');
214
+ const one = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings?limit=1');
215
215
  (0, chai_1.expect)(one.body.recordings.map((s) => s.groupId)).to.deep.equal(['g2']);
216
- const two = yield (0, supertest_1.default)(buildApp(alice)).get(`/xenon/api/recordings?limit=1&cursor=${encodeURIComponent(one.body.nextCursor)}`);
216
+ const two = yield (0, loopbackRequest_1.default)(buildApp(alice)).get(`/xenon/api/recordings?limit=1&cursor=${encodeURIComponent(one.body.nextCursor)}`);
217
217
  (0, chai_1.expect)(two.body.recordings.map((s) => s.groupId)).to.deep.equal(['g1']);
218
- const byPhone = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings?udid=U2');
218
+ const byPhone = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings?udid=U2');
219
219
  (0, chai_1.expect)(byPhone.body.recordings.map((s) => s.groupId)).to.deep.equal(['g2']);
220
220
  }));
221
221
  it('answers 400 for a bad query', () => __awaiter(void 0, void 0, void 0, function* () {
222
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings?since=nope');
222
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings?since=nope');
223
223
  (0, chai_1.expect)(res.status).to.equal(400);
224
224
  (0, chai_1.expect)(res.body.error).to.equal('since must be an ISO time');
225
225
  }));
@@ -238,7 +238,7 @@ describe('recordings library routes', () => {
238
238
  started_at: new Date(now - 40 * 24 * 60 * 60 * 1000),
239
239
  }),
240
240
  ];
241
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
241
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
242
242
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
243
243
  (0, chai_1.expect)(res.body.facets.phones).to.deep.equal([
244
244
  { udid: 'U1', name: 'Galaxy S9+', count: 1 },
@@ -250,7 +250,7 @@ describe('recordings library routes', () => {
250
250
  }));
251
251
  it('names the synthetic user of an auth-disabled server "Auth disabled"', () => __awaiter(void 0, void 0, void 0, function* () {
252
252
  rows = [rec({ started_by: 'auth-disabled' })];
253
- const res = yield (0, supertest_1.default)(buildApp(admin)).get('/xenon/api/recordings');
253
+ const res = yield (0, loopbackRequest_1.default)(buildApp(admin)).get('/xenon/api/recordings');
254
254
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
255
255
  (0, chai_1.expect)(res.body.recordings[0].startedBy).to.deep.equal({
256
256
  id: 'auth-disabled',
@@ -266,7 +266,7 @@ describe('recordings library routes', () => {
266
266
  ctx.pluginArgs = Object.assign(Object.assign({}, original), { recordingCleanupDays: 7, recordingCleanupMaxCount: 20 });
267
267
  try {
268
268
  rows = [rec()];
269
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
269
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
270
270
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
271
271
  (0, chai_1.expect)(res.body.retention).to.deep.equal({ days: 7, maxCount: 20 });
272
272
  }
@@ -296,7 +296,7 @@ describe('recordings library routes', () => {
296
296
  ],
297
297
  }),
298
298
  ];
299
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
299
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
300
300
  (0, chai_1.expect)(res.status).to.equal(200);
301
301
  (0, chai_1.expect)(res.body.summary.groupId).to.equal('g1');
302
302
  (0, chai_1.expect)(res.body.recordings).to.have.length(1);
@@ -312,9 +312,9 @@ describe('recordings library routes', () => {
312
312
  });
313
313
  }));
314
314
  it('answers 404 for a group that does not exist or is not visible', () => __awaiter(void 0, void 0, void 0, function* () {
315
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(admin)).get('/xenon/api/recordings/nope')).status).to.equal(404);
315
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(admin)).get('/xenon/api/recordings/nope')).status).to.equal(404);
316
316
  rows = [rec({ device_udid: 'HIDDEN' })];
317
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
317
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
318
318
  }));
319
319
  it('sorts bookmarks and marks by timecode across every recording in the group', () => __awaiter(void 0, void 0, void 0, function* () {
320
320
  rows = [
@@ -357,7 +357,7 @@ describe('recordings library routes', () => {
357
357
  ],
358
358
  }),
359
359
  ];
360
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
360
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
361
361
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
362
362
  (0, chai_1.expect)(res.body.bookmarks.map((b) => b.id)).to.deep.equal(['b1', 'b2']);
363
363
  (0, chai_1.expect)(res.body.annotations.map((a) => a.id)).to.deep.equal(['a1', 'a2']);
@@ -388,8 +388,8 @@ describe('recordings library routes', () => {
388
388
  });
389
389
  /** hasComposite as the list, then the detail, reports it. */
390
390
  const reported = (who) => __awaiter(void 0, void 0, void 0, function* () {
391
- const list = yield (0, supertest_1.default)(buildApp(who)).get('/xenon/api/recordings');
392
- const detail = yield (0, supertest_1.default)(buildApp(who)).get('/xenon/api/recordings/g1');
391
+ const list = yield (0, loopbackRequest_1.default)(buildApp(who)).get('/xenon/api/recordings');
392
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(who)).get('/xenon/api/recordings/g1');
393
393
  (0, chai_1.expect)(list.status, JSON.stringify(list.body)).to.equal(200);
394
394
  (0, chai_1.expect)(detail.status, JSON.stringify(detail.body)).to.equal(200);
395
395
  return [list.body.recordings[0].hasComposite, detail.body.summary.hasComposite];
@@ -419,7 +419,7 @@ describe('recordings library routes', () => {
419
419
  });
420
420
  });
421
421
  const enabled = (who) => __awaiter(void 0, void 0, void 0, function* () {
422
- const res = yield (0, supertest_1.default)(buildApp(who)).get('/xenon/api/recordings/active');
422
+ const res = yield (0, loopbackRequest_1.default)(buildApp(who)).get('/xenon/api/recordings/active');
423
423
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
424
424
  return res.body.groups.map((g) => [g.groupId, g.compositeEnabled]);
425
425
  });
@@ -435,7 +435,7 @@ describe('recordings library routes', () => {
435
435
  devices.set('U2', { name: 'Team B phone', platform: 'android' });
436
436
  visible = new Set(['U1']);
437
437
  rows = rows.map((r) => (Object.assign(Object.assign({}, r), { annotations: [{ id: `a-${r.id}`, t_ms: 1, kind: 'rect', color: '#f00', payload: '{}' }] })));
438
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/active');
438
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/active');
439
439
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
440
440
  const [group] = res.body.groups;
441
441
  (0, chai_1.expect)(group.recordings.map((r) => r.udid)).to.deep.equal(['U1']);
@@ -452,14 +452,14 @@ describe('recordings library routes', () => {
452
452
  });
453
453
  }),
454
454
  });
455
- const res = yield (0, supertest_1.default)(buildApp(admin)).get('/xenon/api/recordings/active');
455
+ const res = yield (0, loopbackRequest_1.default)(buildApp(admin)).get('/xenon/api/recordings/active');
456
456
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
457
457
  (0, chai_1.expect)(res.body.groups[0].recordings.map((r) => r.udid)).to.deep.equal(['U1', 'U2']);
458
458
  }));
459
459
  });
460
460
  });
461
461
  describe('DELETE /recordings/:groupId', () => {
462
- const del = (who) => (0, supertest_1.default)(buildApp(who)).delete('/xenon/api/recordings/g1');
462
+ const del = (who) => (0, loopbackRequest_1.default)(buildApp(who)).delete('/xenon/api/recordings/g1');
463
463
  it('lets the person who recorded it delete it: rows, then files', () => __awaiter(void 0, void 0, void 0, function* () {
464
464
  rows = [
465
465
  rec(),
@@ -596,20 +596,20 @@ describe('recordings library routes', () => {
596
596
  const unplugged = (over = {}) => rec(Object.assign({ id: 'r2', device_udid: 'GONE', started_at: new Date(T0 + 500) }, over));
597
597
  it('still lists it, and opens it with every phone', () => __awaiter(void 0, void 0, void 0, function* () {
598
598
  rows = [rec({ group_id: 'gone', id: 'solo', device_udid: 'GONE' }), rec(), unplugged()];
599
- const list = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
599
+ const list = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
600
600
  (0, chai_1.expect)(list.status, JSON.stringify(list.body)).to.equal(200);
601
601
  (0, chai_1.expect)(list.body.recordings.map((s) => s.groupId).sort()).to.deep.equal(['g1', 'gone']);
602
602
  const g1 = list.body.recordings.find((s) => s.groupId === 'g1');
603
603
  (0, chai_1.expect)(g1.phones.map((p) => p.udid)).to.deep.equal(['U1', 'GONE']);
604
- const detail = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/gone');
604
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/gone');
605
605
  (0, chai_1.expect)(detail.status, JSON.stringify(detail.body)).to.equal(200);
606
606
  (0, chai_1.expect)(detail.body.summary.phones.map((p) => p.udid)).to.deep.equal(['GONE']);
607
- const both = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
607
+ const both = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
608
608
  (0, chai_1.expect)(both.body.summary.phones.map((p) => p.udid)).to.deep.equal(['U1', 'GONE']);
609
609
  }));
610
610
  it('can still delete it', () => __awaiter(void 0, void 0, void 0, function* () {
611
611
  rows = [rec({ device_udid: 'GONE' })];
612
- const res = yield (0, supertest_1.default)(buildApp(alice)).delete('/xenon/api/recordings/g1');
612
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).delete('/xenon/api/recordings/g1');
613
613
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(204);
614
614
  (0, chai_1.expect)(store.deleteGroupRows.calledOnceWith('g1')).to.equal(true);
615
615
  }));
@@ -617,7 +617,7 @@ describe('recordings library routes', () => {
617
617
  const file = path.join(dir, 'r2.mp4');
618
618
  fs.writeFileSync(file, 'x');
619
619
  rows = [rec(), unplugged({ file_path: file })];
620
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
620
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
621
621
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
622
622
  }));
623
623
  it('is the person who started it, not someone who added a phone later', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -630,14 +630,14 @@ describe('recordings library routes', () => {
630
630
  unplugged(),
631
631
  rec({ id: 'r3', started_by: 'usr_bob', started_at: new Date(T0 + 900) }),
632
632
  ];
633
- const asAlice = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
633
+ const asAlice = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
634
634
  (0, chai_1.expect)(asAlice.body.summary.phones.map((p) => p.recordingId).sort()).to.deep.equal([
635
635
  'r0',
636
636
  'r2',
637
637
  'r3',
638
638
  'rn',
639
639
  ]);
640
- const asBob = yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
640
+ const asBob = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
641
641
  (0, chai_1.expect)(asBob.body.summary.phones.map((p) => p.recordingId).sort()).to.deep.equal([
642
642
  'r0',
643
643
  'r3',
@@ -650,9 +650,9 @@ describe('recordings library routes', () => {
650
650
  const file = path.join(dir, 'r2.mp4');
651
651
  fs.writeFileSync(file, 'x');
652
652
  rows = [rec(), unplugged({ started_by: 'usr_bob', file_path: file })];
653
- const detail = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
653
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
654
654
  (0, chai_1.expect)(detail.body.summary.phones.map((p) => p.recordingId)).to.deep.equal(['r1']);
655
- const src = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
655
+ const src = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
656
656
  (0, chai_1.expect)(src.status).to.equal(404);
657
657
  }));
658
658
  // The exemption is for a phone that is gone, not for one that moved to
@@ -662,13 +662,13 @@ describe('recordings library routes', () => {
662
662
  fs.writeFileSync(file, 'x');
663
663
  devices.set('OTHER', { name: 'Team B phone', platform: 'android' });
664
664
  rows = [rec(), rec({ id: 'r2', device_udid: 'OTHER', file_path: file })];
665
- const list = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
665
+ const list = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
666
666
  (0, chai_1.expect)(list.status, JSON.stringify(list.body)).to.equal(200);
667
667
  (0, chai_1.expect)(list.body.recordings[0].phones.map((p) => p.udid)).to.deep.equal(['U1']);
668
- const detail = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
668
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
669
669
  (0, chai_1.expect)(detail.body.summary.phones.map((p) => p.udid)).to.deep.equal(['U1']);
670
670
  (0, chai_1.expect)(detail.body.recordings.map((r) => r.id)).to.deep.equal(['r1']);
671
- const src = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
671
+ const src = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
672
672
  (0, chai_1.expect)(src.status).to.equal(404);
673
673
  }));
674
674
  // The Delete button shows for the group's owner. A member who only added
@@ -679,23 +679,23 @@ describe('recordings library routes', () => {
679
679
  rec({ device_udid: 'HIDDEN' }),
680
680
  rec({ id: 'r2', started_by: 'usr_bob', started_at: new Date(T0 + 500) }),
681
681
  ];
682
- const list = yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
682
+ const list = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
683
683
  (0, chai_1.expect)(list.body.recordings[0].phones.map((p) => p.recordingId)).to.deep.equal(['r2']);
684
684
  (0, chai_1.expect)(list.body.recordings[0].startedBy).to.deep.equal({ id: 'usr_alice', name: 'Alice' });
685
- const detail = yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
685
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
686
686
  (0, chai_1.expect)(detail.body.summary.startedBy).to.deep.equal({ id: 'usr_alice', name: 'Alice' });
687
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(403);
687
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(403);
688
688
  }));
689
689
  it('does not open it to another member: 404 everywhere', () => __awaiter(void 0, void 0, void 0, function* () {
690
690
  const file = path.join(dir, 'r1.mp4');
691
691
  fs.writeFileSync(file, 'x');
692
692
  rows = [rec({ device_udid: 'GONE', file_path: file })];
693
- const list = yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
693
+ const list = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
694
694
  (0, chai_1.expect)(list.body.recordings).to.deep.equal([]);
695
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
696
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1'))
695
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
696
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1'))
697
697
  .status).to.equal(404);
698
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(404);
698
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(404);
699
699
  (0, chai_1.expect)(store.deleteGroupRows.called).to.equal(false);
700
700
  }));
701
701
  });
@@ -738,7 +738,7 @@ describe('recordings library routes', () => {
738
738
  { id: 'r-a', udid: 'U1', status: 'STOPPED', durationMs: 1000, sizeBytes: 10 },
739
739
  { id: 'r-b', udid: 'U2', status: 'STOPPED', durationMs: 2000, sizeBytes: 20 },
740
740
  ]));
741
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
741
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
742
742
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
743
743
  (0, chai_1.expect)(res.body.groupId).to.equal('g1');
744
744
  (0, chai_1.expect)(res.body.recordings.map((r) => r.id)).to.deep.equal(['r-a']);
@@ -752,7 +752,7 @@ describe('recordings library routes', () => {
752
752
  { id: 'r-a', udid: 'U1', status: 'STOPPED' },
753
753
  { id: 'r-b', udid: 'U2', status: 'STOPPED' },
754
754
  ]));
755
- const res = yield (0, supertest_1.default)(buildApp(admin)).post('/xenon/api/recordings/g1/stop');
755
+ const res = yield (0, loopbackRequest_1.default)(buildApp(admin)).post('/xenon/api/recordings/g1/stop');
756
756
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
757
757
  (0, chai_1.expect)(res.body.recordings.map((r) => r.id).sort()).to.deep.equal(['r-a', 'r-b']);
758
758
  }));
@@ -773,7 +773,7 @@ describe('recordings library routes', () => {
773
773
  { id: 'r-a', udid: 'U1', status: 'STOPPED' },
774
774
  { id: 'r-b', udid: 'U2', status: 'STOPPED' },
775
775
  ]));
776
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
776
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
777
777
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
778
778
  (0, chai_1.expect)(res.body.recordings.map((r) => r.id).sort()).to.deep.equal(['r-a', 'r-b']);
779
779
  }));
@@ -786,21 +786,21 @@ describe('recordings library routes', () => {
786
786
  { id: 'r-a', udid: 'U1', status: 'STOPPED' },
787
787
  { id: 'r-b', udid: 'U2', status: 'STOPPED' },
788
788
  ]));
789
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
789
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
790
790
  (0, chai_1.expect)(res.body.recordings.map((r) => r.id)).to.deep.equal(['r-a']);
791
791
  }));
792
792
  it('still 500s when the orchestrator throws, without ever filtering', () => __awaiter(void 0, void 0, void 0, function* () {
793
793
  twoTeamRows();
794
794
  visible = new Set(['U1']);
795
795
  stop.rejects(new Error('ffmpeg exploded'));
796
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
796
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
797
797
  (0, chai_1.expect)(res.status).to.equal(500);
798
798
  (0, chai_1.expect)(res.body).to.deep.equal({ error: 'internal', message: 'ffmpeg exploded' });
799
799
  }));
800
800
  it('a group with no visible phones is not found, and is not stopped', () => __awaiter(void 0, void 0, void 0, function* () {
801
801
  twoTeamRows();
802
802
  visible = new Set(); // neither U1 nor U2 is on alice's team
803
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
803
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
804
804
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(404);
805
805
  (0, chai_1.expect)(res.body).to.deep.equal({ error: 'not_found' });
806
806
  (0, chai_1.expect)(stop.called).to.equal(false);
@@ -820,7 +820,7 @@ describe('recordings library routes', () => {
820
820
  typedi_1.Container.set(annotation_render_1.AnnotationRenderService, { renderForRecording: render });
821
821
  });
822
822
  afterEach(() => restoreRender());
823
- const get = (who, id) => (0, supertest_1.default)(buildApp(who))
823
+ const get = (who, id) => (0, loopbackRequest_1.default)(buildApp(who))
824
824
  .get(`/xenon/api/recordings/g1/exports/annotated.mp4?recordingId=${id}`)
825
825
  .buffer(true)
826
826
  .parse((r, cb) => {
@@ -865,7 +865,7 @@ describe('recordings library routes', () => {
865
865
  const file = path.join(dir, 'r1.mp4');
866
866
  fs.writeFileSync(file, Buffer.from('0123456789'));
867
867
  rows = [rec({ file_path: file })];
868
- const res = yield (0, supertest_1.default)(buildApp(alice))
868
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice))
869
869
  .get('/xenon/api/recordings/g1/source.mp4?recordingId=r1')
870
870
  .set('Range', 'bytes=2-5')
871
871
  .buffer(true)
@@ -891,7 +891,7 @@ describe('recordings library routes', () => {
891
891
  rec({ file_path: visibleFile }),
892
892
  rec({ id: 'r2', device_udid: 'GONE', file_path: file }),
893
893
  ];
894
- const get = (who, id) => (0, supertest_1.default)(buildApp(who)).get(`/xenon/api/recordings/g1/source.mp4?recordingId=${id}`);
894
+ const get = (who, id) => (0, loopbackRequest_1.default)(buildApp(who)).get(`/xenon/api/recordings/g1/source.mp4?recordingId=${id}`);
895
895
  (0, chai_1.expect)((yield get(bob, 'r1')).status).to.equal(200);
896
896
  (0, chai_1.expect)(store.findVideo.calledWith('r1')).to.equal(true);
897
897
  (0, chai_1.expect)(store.listGroupStarts.called, 'a visible phone needs no owner').to.equal(false);
@@ -908,9 +908,9 @@ describe('recordings library routes', () => {
908
908
  fs.writeFileSync(file, Buffer.from('0123456789'));
909
909
  rows = [rec({ file_path: file })];
910
910
  const url = '/xenon/api/recordings/g1/source.mp4?recordingId=r1&download=1';
911
- const fileEtag = (yield (0, supertest_1.default)(buildApp(alice)).get(url)).headers.etag;
911
+ const fileEtag = (yield (0, loopbackRequest_1.default)(buildApp(alice)).get(url)).headers.etag;
912
912
  (0, chai_1.expect)(fileEtag, "the file's own ETag").to.be.a('string');
913
- const res = yield (0, supertest_1.default)(buildApp(alice)).get(url).set('Range', 'bytes=9999-10000');
913
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get(url).set('Range', 'bytes=9999-10000');
914
914
  (0, chai_1.expect)(res.status).to.equal(416);
915
915
  (0, chai_1.expect)(res.headers['content-type']).to.match(/^application\/json/);
916
916
  (0, chai_1.expect)(res.headers['content-range']).to.equal('bytes */10');
@@ -922,13 +922,13 @@ describe('recordings library routes', () => {
922
922
  const file = path.join(dir, 'r1.mp4');
923
923
  fs.writeFileSync(file, 'x');
924
924
  rows = [rec({ file_path: file })];
925
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1&download=1');
925
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1&download=1');
926
926
  (0, chai_1.expect)(res.status).to.equal(200);
927
927
  (0, chai_1.expect)(res.headers['content-disposition']).to.match(/attachment; filename="U1\.mp4"/);
928
928
  }));
929
929
  it('answers 400 without a recording, and 404 for another group, an invisible phone or a missing file', () => __awaiter(void 0, void 0, void 0, function* () {
930
930
  rows = [rec(), rec({ id: 'rx', group_id: 'g2' }), rec({ id: 'rh', device_udid: 'HIDDEN' })];
931
- const get = (q) => (0, supertest_1.default)(buildApp(bob)).get(`/xenon/api/recordings/g1/source.mp4${q}`);
931
+ const get = (q) => (0, loopbackRequest_1.default)(buildApp(bob)).get(`/xenon/api/recordings/g1/source.mp4${q}`);
932
932
  (0, chai_1.expect)((yield get('')).status).to.equal(400);
933
933
  (0, chai_1.expect)((yield get('?recordingId=rx')).status).to.equal(404);
934
934
  (0, chai_1.expect)((yield get('?recordingId=rh')).status).to.equal(404);
@@ -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 recordings_1 = __importDefault(require("../../src/app/routers/recordings"));
55
55
  const RecordingOrchestrator_1 = require("../../src/services/recording/RecordingOrchestrator");
@@ -164,7 +164,7 @@ describe('recording write routes: team visibility', () => {
164
164
  const alice = { userId: 'usr_alice', teamIds: ['t1'] };
165
165
  const bob = { userId: 'usr_bob', teamIds: ['t1'] };
166
166
  const admin = { userId: 'usr_root', role: 'ADMIN' };
167
- const post = (who, url, body = {}) => (0, supertest_1.default)(buildApp(who)).post(`/xenon/api/recordings${url}`).send(body);
167
+ const post = (who, url, body = {}) => (0, loopbackRequest_1.default)(buildApp(who)).post(`/xenon/api/recordings${url}`).send(body);
168
168
  const notFound = (res) => {
169
169
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(404);
170
170
  (0, chai_1.expect)(res.body).to.deep.equal({ error: 'not_found' });
@@ -0,0 +1,130 @@
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 express_1 = __importDefault(require("express"));
52
+ const typedi_1 = require("typedi");
53
+ const SessionLifecycleService_1 = require("../../src/services/SessionLifecycleService");
54
+ const PluginContext_1 = require("../../src/PluginContext");
55
+ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
56
+ const deviceService = __importStar(require("../../src/data-service/device-service"));
57
+ const SessionManager_1 = require("../../src/sessions/SessionManager");
58
+ const SessionType_1 = __importDefault(require("../../src/enums/SessionType"));
59
+ const nodeWebDriverUrl_1 = require("../../src/gateway/nodeWebDriverUrl");
60
+ const container_registration_1 = require("../helpers/container-registration");
61
+ const loopbackServer_1 = require("../helpers/loopbackServer");
62
+ const loki_stores_1 = require("../helpers/loki-stores");
63
+ /**
64
+ * A session the hub creates on a node's phone (today's createSession path,
65
+ * through the plugin):
66
+ * - it is created under the node's own base path, which need not be the hub's;
67
+ * - it is registered in SESSION_MANAGER whatever the dashboard and video
68
+ * settings, because that is where the session gateway finds it.
69
+ */
70
+ describe('a session the hub creates on a node', () => {
71
+ // The in-memory stores, also when this file runs on its own: without
72
+ // NODE_ENV=test the factory handed out the Prisma stores, which are the
73
+ // developer's ~/.cache/xenon/xenon.db.
74
+ (0, loki_stores_1.useLokiStores)();
75
+ const loopback = (0, loopbackServer_1.loopbackServers)();
76
+ let nodeOrigin;
77
+ let created;
78
+ let context;
79
+ let saved;
80
+ let restore;
81
+ const SESSION = 'node-session-1';
82
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
83
+ created = [];
84
+ const node = (0, express_1.default)();
85
+ node.use(express_1.default.json());
86
+ // The node's Appium runs at the root; the hub's at /wd/hub.
87
+ node.get('/xenon/api/webdriver', (0, nodeWebDriverUrl_1.webdriverInfoHandler)(() => ''));
88
+ node.post('/session', (req, res) => {
89
+ created.push({ url: req.originalUrl, body: req.body });
90
+ res.json({ value: { sessionId: SESSION, capabilities: { platformName: 'Android' } } });
91
+ });
92
+ const server = yield loopback.serve(node);
93
+ nodeOrigin = `http://127.0.0.1:${server.address().port}`;
94
+ restore = (0, container_registration_1.saveRegistrations)(nodeWebDriverUrl_1.NodeBasePathResolver);
95
+ typedi_1.Container.set(nodeWebDriverUrl_1.NodeBasePathResolver, new nodeWebDriverUrl_1.NodeBasePathResolver());
96
+ context = typedi_1.Container.get(PluginContext_1.PluginContext);
97
+ saved = Object.assign({}, context);
98
+ context.pluginArgs = Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { enableDashboard: false });
99
+ context.nodeId = 'hub-node-id';
100
+ context.nodeBasePath = '/wd/hub';
101
+ sinon_1.default.stub(deviceService, 'updatedAllocatedDevice').resolves();
102
+ }));
103
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
104
+ SessionManager_1.SESSION_MANAGER.removeSession(SESSION);
105
+ Object.assign(context, saved);
106
+ sinon_1.default.restore();
107
+ restore();
108
+ yield loopback.closeAll();
109
+ }));
110
+ const device = () => ({ udid: 'u1', host: nodeOrigin, nodeId: 'node-peer', platform: 'android' });
111
+ const caps = {
112
+ alwaysMatch: { platformName: 'Android', 'xe:options': { recordVideo: false } },
113
+ firstMatch: [{}],
114
+ };
115
+ it('is created under the node’s own base path', () => __awaiter(void 0, void 0, void 0, function* () {
116
+ const svc = new SessionLifecycleService_1.SessionLifecycleService();
117
+ const response = yield svc.forwardSessionRequest(device(), caps);
118
+ (0, chai_1.expect)(created.map((c) => c.url)).to.deep.equal(['/session']);
119
+ (0, chai_1.expect)(response.value[0]).to.equal(SESSION);
120
+ }));
121
+ it('is registered in SESSION_MANAGER with the node’s URL, with the dashboard and video off', () => __awaiter(void 0, void 0, void 0, function* () {
122
+ const svc = new SessionLifecycleService_1.SessionLifecycleService();
123
+ sinon_1.default.stub(svc, 'getFreshDevice').callsFake((d) => __awaiter(void 0, void 0, void 0, function* () { return d; }));
124
+ yield svc.finalizeSession({ protocol: 'W3C', value: [SESSION, { platformName: 'Android' }, 'W3C'] }, device(), caps, {}, true);
125
+ const session = SessionManager_1.SESSION_MANAGER.getSession(SESSION);
126
+ (0, chai_1.expect)(session, 'registered').to.not.equal(undefined);
127
+ (0, chai_1.expect)(session.getType()).to.equal(SessionType_1.default.REMOTE);
128
+ (0, chai_1.expect)(session.getWebDriverUrl()).to.equal(nodeOrigin);
129
+ }));
130
+ });
@@ -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 reservation_1 = __importDefault(require("../../src/app/routers/reservation"));
54
54
  const deviceService = __importStar(require("../../src/data-service/device-service"));
55
55
  const prisma_1 = require("../../src/prisma");
@@ -105,13 +105,13 @@ function buildApp(caller) {
105
105
  app.use('/reservation', reservation_1.default);
106
106
  return app;
107
107
  }
108
- const reserve = (caller, udid) => (0, supertest_1.default)(buildApp(caller))
108
+ const reserve = (caller, udid) => (0, loopbackRequest_1.default)(buildApp(caller))
109
109
  .post('/reservation')
110
110
  .send({ udid, host: HOST, reservedBy: 'alice', duration: '1h' });
111
- const extend = (caller, udid) => (0, supertest_1.default)(buildApp(caller))
111
+ const extend = (caller, udid) => (0, loopbackRequest_1.default)(buildApp(caller))
112
112
  .post(`/reservation/${udid}/${encodeURIComponent(HOST)}/extend`)
113
113
  .send({ duration: '1h' });
114
- const release = (caller, udid) => (0, supertest_1.default)(buildApp(caller)).delete(`/reservation/${udid}/${encodeURIComponent(HOST)}`);
114
+ const release = (caller, udid) => (0, loopbackRequest_1.default)(buildApp(caller)).delete(`/reservation/${udid}/${encodeURIComponent(HOST)}`);
115
115
  describe('reservations respect the team boundary', () => {
116
116
  let reserveDevice;
117
117
  let releaseReservation;
@@ -176,7 +176,7 @@ describe('reservations respect the team boundary', () => {
176
176
  }));
177
177
  describe('the list', () => {
178
178
  const listed = (caller) => __awaiter(void 0, void 0, void 0, function* () {
179
- const res = yield (0, supertest_1.default)(buildApp(caller)).get('/reservation');
179
+ const res = yield (0, loopbackRequest_1.default)(buildApp(caller)).get('/reservation');
180
180
  (0, chai_1.expect)(res.status).to.equal(200);
181
181
  return res.body.reservations.map((r) => r.udid).sort();
182
182
  });