@xenon-device-management/xenon 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/README.md +2 -1
  2. package/lib/package.json +1 -2
  3. package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-C-Pw_hvw.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-DmYQ0oSk.js} +1 -1
  5. package/lib/public/assets/BugReportButton-n8T1dFUp.js +10 -0
  6. package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-D-rGOBbJ.js} +1 -1
  7. package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-DExETevp.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-CCdFlZm6.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-DiUSOSL-.js} +1 -1
  10. package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-ZOfpH6gh.js} +1 -1
  11. package/lib/public/assets/{Modal-CceoMY7D.js → Modal-CZNtoB-1.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-BKp6h8Jl.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-Bf881FX4.js} +1 -1
  14. package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-EUoyizvW.js} +1 -1
  15. package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-ClT4hf4_.js} +1 -1
  16. package/lib/public/assets/{Table-LV7zFyZg.js → Table-ucR4bnmc.js} +1 -1
  17. package/lib/public/assets/{activity-D17TUloi.js → activity-K-hdylwq.js} +1 -1
  18. package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-BLhdwI3c.js} +1 -1
  19. package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-BbiN7rBI.js} +1 -1
  20. package/lib/public/assets/{apps-Dbnzd0fW.js → apps-Dez1petz.js} +1 -1
  21. package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-UjmFHLAJ.js} +1 -1
  22. package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-BVyEigbu.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-B-ZlBof3.js} +1 -1
  24. package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-DAQjAgUv.js} +2 -2
  25. package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-CmIPa5JK.js} +1 -1
  26. package/lib/public/assets/{button-xtAhYR-f.js → button-Dd-IhMC1.js} +1 -1
  27. package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-CAIeJwa5.js} +1 -1
  28. package/lib/public/assets/{check-CyuEoKkd.js → check-C4Jrjg8f.js} +1 -1
  29. package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-BGzlf-4i.js} +1 -1
  30. package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-_wYqDjDC.js} +1 -1
  31. package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-Ckn00yMB.js} +1 -1
  32. package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-pdVkomLa.js} +1 -1
  33. package/lib/public/assets/{clock-Cd-t7xpe.js → clock-B8vmwkHj.js} +1 -1
  34. package/lib/public/assets/{copy-C6zerX3I.js → copy-BAqnFI-F.js} +1 -1
  35. package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CYq_4F_N.js} +3 -3
  36. package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
  37. package/lib/public/assets/{download-x9WP7mcL.js → download-B6UEAp_1.js} +1 -1
  38. package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-CLqNvmrN.js} +1 -1
  39. package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-Da90BBbT.js} +1 -1
  40. package/lib/public/assets/{index-BR1wTIPC.js → index-BuIIcmGp.js} +2 -2
  41. package/lib/public/assets/{index-BvH_ekRy.js → index-MX7IzqyW.js} +1 -1
  42. package/lib/public/assets/{input-DejcCiTI.js → input-Bq9aY-We.js} +1 -1
  43. package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CIhXIJyP.js} +1 -1
  44. package/lib/public/assets/{layers-BKqYLiHE.js → layers-bXjpswQI.js} +1 -1
  45. package/lib/public/assets/{lock-LFsFhc17.js → lock-pJ72y5Fy.js} +1 -1
  46. package/lib/public/assets/{login-x2pkzt-t.js → login-UOcHALV2.js} +1 -1
  47. package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-XnJgp5yb.js} +1 -1
  48. package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-QkAhI1Jx.js} +1 -1
  49. package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-B42Da0m4.js} +1 -1
  50. package/lib/public/assets/{network-Z_zBhpsD.js → network-CKVqcyp6.js} +1 -1
  51. package/lib/public/assets/{overview-BY5uv08S.js → overview-BORGkN3N.js} +1 -1
  52. package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CKWFOjOI.js} +1 -1
  53. package/lib/public/assets/{plus-BTD0dnQ0.js → plus-BpFKVdC8.js} +1 -1
  54. package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-CcbKBicY.js} +1 -1
  55. package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BA0W9bxl.js} +1 -1
  56. package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Cp7ZBq3s.js} +1 -1
  57. package/lib/public/assets/{select-Uc9E5pye.js → select-DIZWIoVR.js} +1 -1
  58. package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C_4YuvAp.js} +1 -1
  59. package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CXjD_Xth.js} +1 -1
  60. package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CTqIHeoG.js} +1 -1
  61. package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-B0KYkk8z.js} +1 -1
  62. package/lib/public/assets/{settings-CkSWiZq8.js → settings-BcRBKUXd.js} +1 -1
  63. package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-Dh2PXSf_.js} +1 -1
  64. package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-_sXqZztt.js} +1 -1
  65. package/lib/public/assets/{teams-PpoEQz31.js → teams-DGHT8MKm.js} +1 -1
  66. package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV88cV3Z.js} +1 -1
  67. package/lib/public/assets/{upload-C3i7sAZl.js → upload-CIDH9wkj.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-DYGuSNwl.js} +1 -1
  69. package/lib/public/assets/{users-CEJ3fC78.js → users-7yviqflc.js} +1 -1
  70. package/lib/public/assets/{users-Drd7ifJx.js → users-BUE7MoTq.js} +1 -1
  71. package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-BTRxtlGo.js} +1 -1
  72. package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-DMKD9Pgm.js} +1 -1
  73. package/lib/public/assets/{zap-4yN8Jdfw.js → zap-CWE94qcK.js} +1 -1
  74. package/lib/public/index.html +1 -1
  75. package/lib/src/InternalHttpClient.js +5 -1
  76. package/lib/src/XenonCapabilityManager.js +15 -1
  77. package/lib/src/app/appiumBasePath.js +32 -0
  78. package/lib/src/app/index.js +12 -0
  79. package/lib/src/app/insertBeforeRoutes.js +24 -4
  80. package/lib/src/app/registerCommandAuth.js +152 -33
  81. package/lib/src/app/routers/control.js +57 -13
  82. package/lib/src/app/routers/grid.js +28 -8
  83. package/lib/src/app/ws/h264StreamWs.js +13 -8
  84. package/lib/src/app/ws/logcatWs.js +12 -7
  85. package/lib/src/app/ws/upgradeRouter.js +215 -0
  86. package/lib/src/dashboard/event-manager.js +7 -6
  87. package/lib/src/data-service/device-service.js +152 -49
  88. package/lib/src/data-service/device-store.js +81 -10
  89. package/lib/src/data-service/deviceClaims.js +98 -0
  90. package/lib/src/data-service/deviceFieldOwners.js +32 -10
  91. package/lib/src/data-service/prisma-store.js +94 -31
  92. package/lib/src/device-managers/AndroidDeviceManager.js +13 -15
  93. package/lib/src/device-managers/HealthMonitorService.js +17 -2
  94. package/lib/src/device-managers/IOSDeviceManager.js +5 -1
  95. package/lib/src/device-managers/NodeDevices.js +7 -2
  96. package/lib/src/device-managers/android/AndroidH264StreamService.js +3 -3
  97. package/lib/src/device-managers/android/AndroidStreamService.js +6 -5
  98. package/lib/src/device-managers/android/previewHold.js +2 -2
  99. package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
  100. package/lib/src/device-managers/ios/IOSStreamService.js +158 -38
  101. package/lib/src/device-managers/localDeviceHosts.js +86 -0
  102. package/lib/src/device-managers/ownDeviceRow.js +38 -0
  103. package/lib/src/device-utils.js +78 -24
  104. package/lib/src/gateway/createHandoff.js +73 -0
  105. package/lib/src/gateway/defaultGateway.js +80 -0
  106. package/lib/src/gateway/forwardToNode.js +147 -0
  107. package/lib/src/gateway/hubSessionToken.js +356 -0
  108. package/lib/src/gateway/internalCall.js +90 -0
  109. package/lib/src/gateway/nodeSessionStatus.js +140 -0
  110. package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
  111. package/lib/src/gateway/sessionCreate.js +136 -0
  112. package/lib/src/gateway/sessionGateway.js +201 -0
  113. package/lib/src/gateway/sessionLocator.js +100 -0
  114. package/lib/src/generated/client/edge.js +7 -4
  115. package/lib/src/generated/client/index-browser.js +4 -1
  116. package/lib/src/generated/client/index.d.ts +112 -1
  117. package/lib/src/generated/client/index.js +7 -4
  118. package/lib/src/generated/client/package.json +1 -1
  119. package/lib/src/generated/client/schema.prisma +9 -0
  120. package/lib/src/generated/client/wasm.js +4 -1
  121. package/lib/src/helpers/index.js +5 -1
  122. package/lib/src/logger.js +5 -0
  123. package/lib/src/middleware/authMiddleware.js +80 -0
  124. package/lib/src/middleware/commandAuth.js +31 -17
  125. package/lib/src/middleware/csrfMiddleware.js +3 -2
  126. package/lib/src/middleware/sessionListingFilter.js +153 -0
  127. package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
  128. package/lib/src/plugin.js +27 -1
  129. package/lib/src/prisma.js +2 -1
  130. package/lib/src/services/DeviceReconciler.js +5 -2
  131. package/lib/src/services/OrphanSweeper.js +12 -19
  132. package/lib/src/services/PortAllocator.js +75 -16
  133. package/lib/src/services/ServerManager.js +50 -30
  134. package/lib/src/services/SessionLifecycleService.js +335 -76
  135. package/lib/src/services/ShutdownCoordinator.js +7 -1
  136. package/lib/src/services/SocketServer.js +11 -2
  137. package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
  138. package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
  139. package/lib/src/services/lease/LeaseOrphanSweeper.js +32 -6
  140. package/lib/src/services/lease/LeaseService.js +19 -5
  141. package/lib/src/services/session/nodeCreateCaps.js +55 -0
  142. package/lib/src/services/session/sessionCredentials.js +7 -59
  143. package/lib/src/sessions/CloudSession.js +12 -0
  144. package/lib/src/sessions/LocalSession.js +52 -26
  145. package/lib/src/sessions/RemoteSession.js +116 -9
  146. package/lib/src/sessions/SessionManager.js +49 -6
  147. package/lib/src/sessions/appiumUmbrella.js +32 -0
  148. package/lib/test/helpers/appium-umbrella.js +84 -0
  149. package/lib/test/helpers/fake-device-table.js +97 -0
  150. package/lib/test/helpers/loki-stores.js +41 -0
  151. package/lib/test/helpers/loopbackRequest.js +77 -0
  152. package/lib/test/helpers/scratch-database.js +100 -0
  153. package/lib/test/helpers/scratch-port-leases.js +119 -0
  154. package/lib/test/helpers/test-container.js +35 -27
  155. package/lib/test/integration/androidDevices.spec.js +5 -4
  156. package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
  157. package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
  158. package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
  159. package/lib/test/unit/AndroidDeviceManager.spec.js +8 -1
  160. package/lib/test/unit/DeviceModel.spec.js +9 -9
  161. package/lib/test/unit/HealingCascade.test.js +5 -1
  162. package/lib/test/unit/OrphanSweeper.test.js +17 -13
  163. package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
  164. package/lib/test/unit/StreamTicketService.spec.js +4 -1
  165. package/lib/test/unit/TokenEndpoint.spec.js +17 -3
  166. package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
  167. package/lib/test/unit/android-h264-service.spec.js +6 -0
  168. package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
  169. package/lib/test/unit/apps-team-visibility.spec.js +16 -16
  170. package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
  171. package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
  172. package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
  173. package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
  174. package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
  175. package/lib/test/unit/authMiddleware.test.js +3 -0
  176. package/lib/test/unit/bug-report/route.spec.js +9 -9
  177. package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
  178. package/lib/test/unit/clipboard-route.spec.js +18 -8
  179. package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
  180. package/lib/test/unit/command-auth.spec.js +31 -31
  181. package/lib/test/unit/control-device-lookup.spec.js +9 -9
  182. package/lib/test/unit/control-forward-decision.spec.js +115 -0
  183. package/lib/test/unit/control-lookup-failure.spec.js +3 -3
  184. package/lib/test/unit/create-error-message.spec.js +43 -0
  185. package/lib/test/unit/create-handoff.spec.js +86 -0
  186. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  187. package/lib/test/unit/device-access-guard.spec.js +28 -28
  188. package/lib/test/unit/device-service.spec.js +4 -0
  189. package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
  190. package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
  191. package/lib/test/unit/device-team-guard.spec.js +20 -20
  192. package/lib/test/unit/device-utils.spec.js +8 -0
  193. package/lib/test/unit/extract-team-cap.spec.js +65 -0
  194. package/lib/test/unit/grid-admin-only.spec.js +3 -3
  195. package/lib/test/unit/healing-tier-gate.spec.js +5 -1
  196. package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
  197. package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
  198. package/lib/test/unit/helpers.spec.js +27 -0
  199. package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
  200. package/lib/test/unit/hub-create-once.spec.js +190 -0
  201. package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
  202. package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
  203. package/lib/test/unit/hub-node-control-forward.spec.js +291 -0
  204. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
  205. package/lib/test/unit/hub-node-race.spec.js +401 -0
  206. package/lib/test/unit/hub-node-same-host.spec.js +356 -0
  207. package/lib/test/unit/hub-register-removal.spec.js +165 -0
  208. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
  209. package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
  210. package/lib/test/unit/hub-session-token.spec.js +265 -0
  211. package/lib/test/unit/insert-before-routes.spec.js +5 -5
  212. package/lib/test/unit/install-repository-app-team.spec.js +2 -2
  213. package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
  214. package/lib/test/unit/internal-call.spec.js +181 -0
  215. package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
  216. package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
  217. package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
  218. package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
  219. package/lib/test/unit/is-xenon-running.spec.js +69 -0
  220. package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
  221. package/lib/test/unit/lease/LeaseService.spec.js +28 -1
  222. package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
  223. package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +92 -0
  224. package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
  225. package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
  226. package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
  227. package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
  228. package/lib/test/unit/logcat-ws.spec.js +4 -1
  229. package/lib/test/unit/loopback-request.spec.js +81 -0
  230. package/lib/test/unit/node-create-caps.spec.js +54 -0
  231. package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
  232. package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
  233. package/lib/test/unit/omni-vision.spec.js +7 -3
  234. package/lib/test/unit/plugin.spec.js +45 -4
  235. package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
  236. package/lib/test/unit/profile-router.test.js +11 -11
  237. package/lib/test/unit/queue-requester.spec.js +5 -0
  238. package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
  239. package/lib/test/unit/queue-visibility.spec.js +1 -1
  240. package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
  241. package/lib/test/unit/recording-instrumentation.spec.js +5 -0
  242. package/lib/test/unit/recording-source-ended.spec.js +5 -0
  243. package/lib/test/unit/recording-store.spec.js +9 -3
  244. package/lib/test/unit/recordings-library-routes.spec.js +51 -51
  245. package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
  246. package/lib/test/unit/remote-session-create.spec.js +130 -0
  247. package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
  248. package/lib/test/unit/reset-link.test.js +14 -14
  249. package/lib/test/unit/session-app-resolution.spec.js +14 -10
  250. package/lib/test/unit/session-assets.spec.js +9 -9
  251. package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
  252. package/lib/test/unit/session-create-layer.spec.js +255 -0
  253. package/lib/test/unit/session-credential-strip.spec.js +25 -9
  254. package/lib/test/unit/session-credentials.spec.js +0 -47
  255. package/lib/test/unit/session-listing-auth.spec.js +345 -0
  256. package/lib/test/unit/session-locator.spec.js +250 -0
  257. package/lib/test/unit/session-node-calls.spec.js +174 -0
  258. package/lib/test/unit/session-owner-resolver.spec.js +107 -0
  259. package/lib/test/unit/session-recovery-routing.spec.js +118 -0
  260. package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
  261. package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
  262. package/lib/test/unit/socketServer.bearer.spec.js +3 -1
  263. package/lib/test/unit/stream-leave-route.spec.js +15 -7
  264. package/lib/test/unit/stream-own-device-row.spec.js +164 -0
  265. package/lib/test/unit/stream-stop-ownership.spec.js +13 -5
  266. package/lib/test/unit/stream-stop-recording.spec.js +13 -5
  267. package/lib/test/unit/stream-ticket-identity.spec.js +15 -5
  268. package/lib/test/unit/upgrade-router.spec.js +446 -0
  269. package/lib/test/unit/users-router.test.js +11 -8
  270. package/lib/test/unit/verify-credential.spec.js +4 -1
  271. package/lib/test/unit/webdriver-info-route.spec.js +64 -0
  272. package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
  273. package/lib/tsconfig.tsbuildinfo +1 -1
  274. package/package.json +1 -2
  275. package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
  276. package/prisma/schema.prisma +9 -0
  277. package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
  278. package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
  279. package/lib/src/proxy/wd-command-proxy.js +0 -221
@@ -49,7 +49,7 @@ require("reflect-metadata");
49
49
  const chai_1 = require("chai");
50
50
  const sinon_1 = __importDefault(require("sinon"));
51
51
  const express_1 = __importDefault(require("express"));
52
- const supertest_1 = __importDefault(require("supertest"));
52
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
53
53
  const typedi_1 = require("typedi");
54
54
  const prisma_1 = require("../../src/prisma");
55
55
  const config_1 = require("../../src/config");
@@ -125,7 +125,7 @@ describe('GET /xenon/api/cliArgs', () => {
125
125
  sinon_1.default.restore();
126
126
  config_1.config.authDisabled = savedAuthDisabled;
127
127
  });
128
- const asCookie = (sessionId) => (0, supertest_1.default)(app).get('/xenon/api/cliArgs').set('Cookie', `xenon_dashboard_session=${sessionId}`);
128
+ const asCookie = (sessionId) => (0, loopbackRequest_1.default)(app).get('/xenon/api/cliArgs').set('Cookie', `xenon_dashboard_session=${sessionId}`);
129
129
  function asApiKey(scopes, ownerId) {
130
130
  sinon_1.default.stub(typedi_1.Container.get(ApiKeyService_1.ApiKeyService), 'verifyPair').resolves({
131
131
  id: 'key-1',
@@ -138,7 +138,7 @@ describe('GET /xenon/api/cliArgs', () => {
138
138
  userId: ownerId,
139
139
  teamId: null,
140
140
  });
141
- return (0, supertest_1.default)(app)
141
+ return (0, loopbackRequest_1.default)(app)
142
142
  .get('/xenon/api/cliArgs')
143
143
  .set('x-xenon-access-key', 'ak')
144
144
  .set('x-xenon-token', 'tok');
@@ -16,7 +16,7 @@ require("reflect-metadata");
16
16
  const chai_1 = require("chai");
17
17
  const sinon_1 = __importDefault(require("sinon"));
18
18
  const express_1 = __importDefault(require("express"));
19
- const supertest_1 = __importDefault(require("supertest"));
19
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
20
20
  const typedi_1 = require("typedi");
21
21
  const control_1 = __importDefault(require("../../src/app/routers/control"));
22
22
  const device_store_1 = require("../../src/data-service/device-store");
@@ -24,6 +24,8 @@ const ApiKeyService_1 = require("../../src/services/ApiKeyService");
24
24
  const device_managers_1 = require("../../src/device-managers");
25
25
  const clipboardErrors_1 = require("../../src/device-managers/clipboardErrors");
26
26
  const authMiddleware_1 = require("../../src/middleware/authMiddleware");
27
+ const container_registration_1 = require("../helpers/container-registration");
28
+ const loopbackServer_1 = require("../helpers/loopbackServer");
27
29
  /**
28
30
  * GET/POST /control/:udid/clipboard answered 200 whatever happened on the
29
31
  * device: the Android manager swallowed its errors, and its "set" had no
@@ -57,7 +59,14 @@ function buildApp() {
57
59
  }
58
60
  describe('GET/POST /control/:udid/clipboard', () => {
59
61
  let manager;
62
+ let restoreContainer;
63
+ // Served on 127.0.0.1, not by request(app): that listens on every address
64
+ // and connects to 127.0.0.1, where another process's listener on the same
65
+ // port answers instead (a stray 501 or a socket hang-up in a full run).
66
+ const loopback = (0, loopbackServer_1.loopbackServers)();
67
+ const app = () => __awaiter(void 0, void 0, void 0, function* () { return loopback.serve(buildApp()); });
60
68
  beforeEach(() => {
69
+ restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, device_managers_1.XenonManager);
61
70
  manager = new AndroidDeviceManager();
62
71
  sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
63
72
  findDevice: () => __awaiter(void 0, void 0, void 0, function* () {
@@ -73,19 +82,20 @@ describe('GET/POST /control/:udid/clipboard', () => {
73
82
  typedi_1.Container.set(ApiKeyService_1.ApiKeyService, new ApiKeyService_1.ApiKeyService());
74
83
  typedi_1.Container.set(device_managers_1.XenonManager, { deviceInstances: () => __awaiter(void 0, void 0, void 0, function* () { return [manager]; }) });
75
84
  });
76
- afterEach(() => {
85
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
77
86
  sinon_1.default.restore();
78
- typedi_1.Container.reset();
79
- });
87
+ restoreContainer();
88
+ yield loopback.closeAll();
89
+ }));
80
90
  it('returns the clipboard text', () => __awaiter(void 0, void 0, void 0, function* () {
81
91
  manager.getClipboard.resolves('xenonread');
82
- const res = yield (0, supertest_1.default)(buildApp()).get(`/xenon/api/control/${UDID}/clipboard`);
92
+ const res = yield (0, loopbackRequest_1.default)(yield app()).get(`/xenon/api/control/${UDID}/clipboard`);
83
93
  (0, chai_1.expect)(res.status).to.equal(200);
84
94
  (0, chai_1.expect)(res.body).to.deep.equal({ content: 'xenonread' });
85
95
  }));
86
96
  it('says why a read failed, with a 500', () => __awaiter(void 0, void 0, void 0, function* () {
87
97
  manager.getClipboard.rejects(new Error('Appium Settings didn’t return the clipboard.'));
88
- const res = yield (0, supertest_1.default)(buildApp()).get(`/xenon/api/control/${UDID}/clipboard`);
98
+ const res = yield (0, loopbackRequest_1.default)(yield app()).get(`/xenon/api/control/${UDID}/clipboard`);
89
99
  (0, chai_1.expect)(res.status).to.equal(500);
90
100
  (0, chai_1.expect)(res.body.error).to.match(/Appium Settings/);
91
101
  }));
@@ -93,7 +103,7 @@ describe('GET/POST /control/:udid/clipboard', () => {
93
103
  // won't help. 501 says exactly that.
94
104
  it('answers 501 when the device cannot set its clipboard', () => __awaiter(void 0, void 0, void 0, function* () {
95
105
  manager.setClipboard.rejects(new clipboardErrors_1.ClipboardUnsupportedError('Appium Settings can only read it.'));
96
- const res = yield (0, supertest_1.default)(buildApp())
106
+ const res = yield (0, loopbackRequest_1.default)(yield app())
97
107
  .post(`/xenon/api/control/${UDID}/clipboard`)
98
108
  .send({ content: 'x' });
99
109
  (0, chai_1.expect)(res.status).to.equal(501);
@@ -101,7 +111,7 @@ describe('GET/POST /control/:udid/clipboard', () => {
101
111
  }));
102
112
  it('still answers 500 for any other write failure', () => __awaiter(void 0, void 0, void 0, function* () {
103
113
  manager.setClipboard.rejects(new Error('WDA is not running'));
104
- const res = yield (0, supertest_1.default)(buildApp())
114
+ const res = yield (0, loopbackRequest_1.default)(yield app())
105
115
  .post(`/xenon/api/control/${UDID}/clipboard`)
106
116
  .send({ content: 'x' });
107
117
  (0, chai_1.expect)(res.status).to.equal(500);
@@ -1,4 +1,37 @@
1
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
+ })();
2
35
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
36
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
37
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -16,7 +49,8 @@ const chai_1 = require("chai");
16
49
  const http_1 = __importDefault(require("http"));
17
50
  const path_1 = __importDefault(require("path"));
18
51
  const sinon_1 = __importDefault(require("sinon"));
19
- const supertest_1 = __importDefault(require("supertest"));
52
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
53
+ const ws_1 = __importStar(require("ws"));
20
54
  const commandCaller_1 = require("../../src/middleware/commandCaller");
21
55
  const commandAuth_1 = require("../../src/middleware/commandAuth");
22
56
  const registerCommandAuth_1 = require("../../src/app/registerCommandAuth");
@@ -29,9 +63,22 @@ const registerCommandAuth_1 = require("../../src/app/registerCommandAuth");
29
63
  * Express 5 as Appium builds it: its middleware, then every WebDriver route,
30
64
  * then the plugin updaters, then its catch-all 404. The fake driver records
31
65
  * each command that reaches it, which shows whether the check ran first.
66
+ *
67
+ * The session listing and the BiDi WebSocket go through the same server:
68
+ * registerSessionUpgradeGuard is a server updater too, and the BiDi handler is
69
+ * added after boot with addWebSocketHandler, as Appium's main adds it.
32
70
  */
33
71
  const EXACT_UNKNOWN_SESSION = JSON.stringify(commandAuth_1.UNKNOWN_SESSION_BODY);
34
72
  const LIVE_SESSION = 'live-session';
73
+ const BOBS_SESSION = 'bobs-session';
74
+ const LISTING = [
75
+ { id: LIVE_SESSION, created: 1, capabilities: { platformName: 'Fake' } },
76
+ { id: BOBS_SESSION, created: 2, capabilities: { platformName: 'Fake' } },
77
+ ];
78
+ const OWNERS = { [LIVE_SESSION]: 'alice', [BOBS_SESSION]: 'bob' };
79
+ function nativelyUsesUpgradeListener() {
80
+ return typeof http_1.default.createServer().shouldUpgradeCallback !== 'undefined';
81
+ }
35
82
  function appiumBaseDriver() {
36
83
  const appiumDir = path_1.default.dirname(require.resolve('appium/package.json'));
37
84
  // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -47,11 +94,14 @@ describe("per-command auth in Appium 3's own server()", () => {
47
94
  let server;
48
95
  let commands;
49
96
  let enabled;
97
+ let infos;
50
98
  let warnings;
99
+ let bidiConnections;
51
100
  function boot() {
52
- return __awaiter(this, void 0, void 0, function* () {
101
+ return __awaiter(this, arguments, void 0, function* (opts = {}) {
53
102
  const baseDriver = appiumBaseDriver();
54
103
  commands = [];
104
+ bidiConnections = [];
55
105
  const driver = {
56
106
  log: quiet,
57
107
  protocol: 'W3C',
@@ -63,44 +113,88 @@ describe("per-command auth in Appium 3's own server()", () => {
63
113
  commands.push(command);
64
114
  if (command === 'createSession')
65
115
  return [LIVE_SESSION, {}];
116
+ if (command === 'getAppiumSessions')
117
+ return LISTING;
66
118
  return 'about:blank';
67
119
  }),
68
120
  };
121
+ const credentials = { 'alice-token': 'alice', 'bob-token': 'bob' };
69
122
  const verifier = new commandCaller_1.CommandCallerVerifier({
70
123
  verifyKeyPair: sinon_1.default.stub().callsFake((_ak, token) => __awaiter(this, void 0, void 0, function* () {
71
- return token === 'alice-token'
124
+ return credentials[token]
72
125
  ? {
73
- row: { id: 'k', userId: 'alice', scopes: 'sessions', expiresAt: null },
74
- user: { id: 'alice', role: 'MEMBER', status: 'ACTIVE' },
126
+ row: { id: 'k', userId: credentials[token], scopes: 'sessions', expiresAt: null },
127
+ user: { id: credentials[token], role: 'MEMBER', status: 'ACTIVE' },
75
128
  }
76
129
  : null;
77
130
  })),
78
131
  verifyBearer: sinon_1.default.stub().resolves(null),
79
132
  });
80
- server = yield baseDriver.server({
81
- routeConfiguringFunction: baseDriver.routeConfiguringFunction(driver),
82
- port: 0,
83
- hostname: '127.0.0.1',
84
- basePath: '/wd/hub',
85
- // Appium hands plugins the raw CLI args; the base path here is normalised
86
- // by registerCommandAuth itself.
87
- cliArgs: { basePath: 'wd/hub/' },
88
- serverUpdaters: [
89
- (app, _httpServer, cliArgs) => {
90
- (0, registerCommandAuth_1.registerCommandAuth)(app, cliArgs, {
91
- enabled: () => enabled,
92
- authDisabled: () => false,
93
- verifier,
94
- ownerOf: (id) => __awaiter(this, void 0, void 0, function* () { return (id === LIVE_SESSION ? 'alice' : null); }),
95
- logger: {
96
- info: () => undefined,
97
- warn: (m) => warnings.push(m),
98
- error: () => undefined,
99
- },
100
- });
101
- },
102
- ],
133
+ const deps = {
134
+ enabled: () => enabled,
135
+ authDisabled: () => false,
136
+ verifier,
137
+ ownerOf: (id) => __awaiter(this, void 0, void 0, function* () { var _a; return (_a = OWNERS[id]) !== null && _a !== void 0 ? _a : null; }),
138
+ ownersOf: (ids) => __awaiter(this, void 0, void 0, function* () { return new Map(ids.map((id) => { var _a; return [id, (_a = OWNERS[id]) !== null && _a !== void 0 ? _a : null]; })); }),
139
+ logger: {
140
+ info: (m) => infos.push(m),
141
+ warn: (m) => warnings.push(m),
142
+ error: () => undefined,
143
+ },
144
+ };
145
+ const createServer = http_1.default.createServer;
146
+ const forced = opts.upgrades === 'listener' ? sinon_1.default.stub(http_1.default, 'createServer') : undefined;
147
+ forced === null || forced === void 0 ? void 0 : forced.callsFake(((app) => {
148
+ const s = createServer(app);
149
+ if (typeof s.shouldUpgradeCallback === 'undefined') {
150
+ s.shouldUpgradeCallback = () => true;
151
+ }
152
+ return s;
153
+ }));
154
+ try {
155
+ server = yield baseDriver.server({
156
+ routeConfiguringFunction: baseDriver.routeConfiguringFunction(driver),
157
+ port: 0,
158
+ hostname: '127.0.0.1',
159
+ basePath: '/wd/hub',
160
+ // Appium hands plugins the raw CLI args; the base path here is normalised
161
+ // by registerCommandAuth itself.
162
+ cliArgs: { basePath: 'wd/hub/' },
163
+ serverUpdaters: [
164
+ (app, httpServer, cliArgs) => {
165
+ (0, registerCommandAuth_1.registerCommandAuth)(app, cliArgs, deps);
166
+ (0, registerCommandAuth_1.registerSessionUpgradeGuard)(httpServer, app, cliArgs, deps);
167
+ },
168
+ ],
169
+ });
170
+ }
171
+ finally {
172
+ forced === null || forced === void 0 ? void 0 : forced.restore();
173
+ }
174
+ // Appium's main adds its BiDi handler right after server() resolves.
175
+ const bidi = new ws_1.WebSocketServer({ noServer: true });
176
+ bidi.on('connection', (ws, req) => {
177
+ bidiConnections.push(String(req.url));
178
+ ws.on('error', () => undefined);
179
+ });
180
+ yield booted().addWebSocketHandler('/wd/hub/bidi', bidi);
181
+ yield booted().addWebSocketHandler('/wd/hub/bidi/:sessionId', bidi);
182
+ });
183
+ }
184
+ function upgrade(urlPath, headers = {}) {
185
+ const { port } = booted().address();
186
+ return new Promise((resolve) => {
187
+ const ws = new ws_1.default(`ws://127.0.0.1:${port}${urlPath}`, { headers });
188
+ ws.on('open', () => {
189
+ resolve('open');
190
+ ws.terminate();
191
+ });
192
+ ws.on('unexpected-response', (req, res) => {
193
+ var _a;
194
+ resolve((_a = res.statusCode) !== null && _a !== void 0 ? _a : 0);
195
+ req.destroy();
103
196
  });
197
+ ws.on('error', (err) => resolve(err.message));
104
198
  });
105
199
  }
106
200
  // Appium's HTTP logger writes every request to stdout; quiet it here only.
@@ -130,6 +224,7 @@ describe("per-command auth in Appium 3's own server()", () => {
130
224
  }
131
225
  beforeEach(() => {
132
226
  enabled = true;
227
+ infos = [];
133
228
  warnings = [];
134
229
  });
135
230
  afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
@@ -144,7 +239,7 @@ describe("per-command auth in Appium 3's own server()", () => {
144
239
  }));
145
240
  it("refuses a session command before Appium's route runs", () => __awaiter(void 0, void 0, void 0, function* () {
146
241
  yield boot();
147
- const res = yield (0, supertest_1.default)(booted()).get(`/wd/hub/session/${LIVE_SESSION}/url`);
242
+ const res = yield (0, loopbackRequest_1.default)(booted()).get(`/wd/hub/session/${LIVE_SESSION}/url`);
148
243
  (0, chai_1.expect)(res.status).to.equal(404);
149
244
  (0, chai_1.expect)(res.text).to.equal(EXACT_UNKNOWN_SESSION);
150
245
  (0, chai_1.expect)(commands).to.deep.equal([]);
@@ -152,7 +247,7 @@ describe("per-command auth in Appium 3's own server()", () => {
152
247
  }));
153
248
  it("lets the owner through to Appium's route and the driver", () => __awaiter(void 0, void 0, void 0, function* () {
154
249
  yield boot();
155
- const res = yield (0, supertest_1.default)(booted())
250
+ const res = yield (0, loopbackRequest_1.default)(booted())
156
251
  .get(`/wd/hub/session/${LIVE_SESSION}/url`)
157
252
  .set({ 'x-xenon-access-key': 'ak', 'x-xenon-token': 'alice-token' });
158
253
  (0, chai_1.expect)(res.status).to.equal(200);
@@ -161,7 +256,7 @@ describe("per-command auth in Appium 3's own server()", () => {
161
256
  }));
162
257
  it('lets createSession reach the driver without credentials', () => __awaiter(void 0, void 0, void 0, function* () {
163
258
  yield boot();
164
- yield (0, supertest_1.default)(booted())
259
+ yield (0, loopbackRequest_1.default)(booted())
165
260
  .post('/wd/hub/session')
166
261
  .send({ capabilities: { alwaysMatch: { platformName: 'Fake' } } })
167
262
  .expect(200);
@@ -170,7 +265,7 @@ describe("per-command auth in Appium 3's own server()", () => {
170
265
  it("with the setting off, Appium gives its own unknown-session answer, which the refusal matches but for Appium's stack trace", () => __awaiter(void 0, void 0, void 0, function* () {
171
266
  enabled = false;
172
267
  yield boot();
173
- const res = yield (0, supertest_1.default)(booted()).get('/wd/hub/session/no-such-session/url');
268
+ const res = yield (0, loopbackRequest_1.default)(booted()).get('/wd/hub/session/no-such-session/url');
174
269
  (0, chai_1.expect)(res.status).to.equal(404);
175
270
  (0, chai_1.expect)(res.headers['content-type']).to.equal('application/json; charset=utf-8');
176
271
  (0, chai_1.expect)(res.body.value.error).to.equal(commandAuth_1.UNKNOWN_SESSION_BODY.value.error);
@@ -181,4 +276,64 @@ describe("per-command auth in Appium 3's own server()", () => {
181
276
  (0, chai_1.expect)(res.body.value.stacktrace).to.match(/^NoSuchDriverError: A session is either terminated or not started/);
182
277
  (0, chai_1.expect)(warnings).to.deep.equal([]);
183
278
  }));
279
+ describe("Appium's session listing (GET /appium/sessions)", () => {
280
+ it('answers a caller without credentials an empty list, though Appium produced the full one', () => __awaiter(void 0, void 0, void 0, function* () {
281
+ yield boot();
282
+ const res = yield (0, loopbackRequest_1.default)(booted()).get('/wd/hub/appium/sessions');
283
+ (0, chai_1.expect)(res.status).to.equal(200);
284
+ (0, chai_1.expect)(res.headers['content-type']).to.equal('application/json; charset=utf-8');
285
+ (0, chai_1.expect)(res.text).to.equal('{"value":[]}');
286
+ (0, chai_1.expect)(commands).to.deep.equal(['getAppiumSessions']);
287
+ (0, chai_1.expect)(warnings).to.have.length(1);
288
+ }));
289
+ it('shows the owner only their own session', () => __awaiter(void 0, void 0, void 0, function* () {
290
+ yield boot();
291
+ const res = yield (0, loopbackRequest_1.default)(booted())
292
+ .get('/wd/hub/appium/sessions')
293
+ .set({ 'x-xenon-access-key': 'ak', 'x-xenon-token': 'alice-token' });
294
+ (0, chai_1.expect)(res.body).to.deep.equal({ value: [LISTING[0]] });
295
+ (0, chai_1.expect)(infos.some((m) => m.includes('1 of 2'))).to.equal(true);
296
+ }));
297
+ it("with the setting off, answers Appium's listing untouched", () => __awaiter(void 0, void 0, void 0, function* () {
298
+ enabled = false;
299
+ yield boot();
300
+ const res = yield (0, loopbackRequest_1.default)(booted()).get('/wd/hub/appium/sessions');
301
+ (0, chai_1.expect)(res.body).to.deep.equal({ value: LISTING });
302
+ }));
303
+ });
304
+ for (const upgrades of ['native', 'listener']) {
305
+ const how = upgrades === 'listener' || nativelyUsesUpgradeListener()
306
+ ? 'with its upgrade listener'
307
+ : 'with its Express upgrade middleware';
308
+ describe(`the BiDi WebSocket, Appium dispatching upgrades ${how} (${upgrades})`, () => {
309
+ it('refuses a session upgrade without credentials with 404, before Appium attaches it', () => __awaiter(void 0, void 0, void 0, function* () {
310
+ yield boot({ upgrades });
311
+ (0, chai_1.expect)(yield upgrade(`/wd/hub/bidi/${LIVE_SESSION}`)).to.equal(404);
312
+ (0, chai_1.expect)(bidiConnections).to.deep.equal([]);
313
+ (0, chai_1.expect)(warnings).to.have.length(1);
314
+ }));
315
+ it('refuses another user with 404', () => __awaiter(void 0, void 0, void 0, function* () {
316
+ yield boot({ upgrades });
317
+ (0, chai_1.expect)(yield upgrade(`/wd/hub/bidi/${LIVE_SESSION}`, {
318
+ 'x-xenon-access-key': 'ak',
319
+ 'x-xenon-token': 'bob-token',
320
+ })).to.equal(404);
321
+ (0, chai_1.expect)(bidiConnections).to.deep.equal([]);
322
+ }));
323
+ it("lets the owner attach through Appium's own handler", () => __awaiter(void 0, void 0, void 0, function* () {
324
+ yield boot({ upgrades });
325
+ (0, chai_1.expect)(yield upgrade(`/wd/hub/bidi/${LIVE_SESSION}`, {
326
+ 'x-xenon-access-key': 'ak',
327
+ 'x-xenon-token': 'alice-token',
328
+ })).to.equal('open');
329
+ (0, chai_1.expect)(bidiConnections).to.deep.equal([`/wd/hub/bidi/${LIVE_SESSION}`]);
330
+ }));
331
+ it('with the setting off, lets anyone attach, as Appium does', () => __awaiter(void 0, void 0, void 0, function* () {
332
+ enabled = false;
333
+ yield boot({ upgrades });
334
+ (0, chai_1.expect)(yield upgrade(`/wd/hub/bidi/${LIVE_SESSION}`)).to.equal('open');
335
+ (0, chai_1.expect)(warnings).to.deep.equal([]);
336
+ }));
337
+ });
338
+ }
184
339
  });
@@ -16,7 +16,7 @@ const chai_1 = require("chai");
16
16
  const path_1 = __importDefault(require("path"));
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 commandAuth_1 = require("../../src/middleware/commandAuth");
21
21
  const commandCaller_1 = require("../../src/middleware/commandCaller");
22
22
  const loopbackServer_1 = require("../helpers/loopbackServer");
@@ -199,14 +199,14 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
199
199
  describe('who may drive a session', () => {
200
200
  it("lets the owner's key through to the route", () => __awaiter(void 0, void 0, void 0, function* () {
201
201
  const { app, ran } = yield lab();
202
- const res = yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('alice-token'));
202
+ const res = yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('alice-token'));
203
203
  (0, chai_1.expect)(res.status).to.equal(200);
204
204
  (0, chai_1.expect)(ran).to.deep.equal(['getUrl']);
205
205
  (0, chai_1.expect)(warnings).to.deep.equal([]);
206
206
  }));
207
207
  it("lets the owner's Bearer token through", () => __awaiter(void 0, void 0, void 0, function* () {
208
208
  const { app, ran } = yield lab();
209
- yield (0, supertest_1.default)(app)
209
+ yield (0, loopbackRequest_1.default)(app)
210
210
  .get('/wd/hub/session/s-alice/url')
211
211
  .set(bearer('alice-jwt'))
212
212
  .expect(200);
@@ -214,13 +214,13 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
214
214
  }));
215
215
  it('refuses another user with the unknown-session answer, before the route runs', () => __awaiter(void 0, void 0, void 0, function* () {
216
216
  const { app, ran } = yield lab();
217
- const res = yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token'));
217
+ const res = yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token'));
218
218
  yield expectRefused(res);
219
219
  (0, chai_1.expect)(ran).to.deep.equal([]);
220
220
  }));
221
221
  it('logs the refusal with the session and the caller, never the credential', () => __awaiter(void 0, void 0, void 0, function* () {
222
222
  const { app } = yield lab();
223
- yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token'));
223
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token'));
224
224
  (0, chai_1.expect)(warnings).to.have.length(1);
225
225
  (0, chai_1.expect)(warnings[0]).to.include('s-alice');
226
226
  (0, chai_1.expect)(warnings[0]).to.include('bob');
@@ -229,7 +229,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
229
229
  }));
230
230
  it('refuses a request with no credentials, without looking anything up', () => __awaiter(void 0, void 0, void 0, function* () {
231
231
  const { app, ran } = yield lab();
232
- yield expectRefused(yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url'));
232
+ yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url'));
233
233
  (0, chai_1.expect)(ran).to.deep.equal([]);
234
234
  (0, chai_1.expect)(verifyKeyPair.called || verifyBearer.called).to.equal(false);
235
235
  (0, chai_1.expect)(ownerOf.called).to.equal(false);
@@ -238,7 +238,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
238
238
  }));
239
239
  it('refuses credentials that do not verify, without looking up the owner', () => __awaiter(void 0, void 0, void 0, function* () {
240
240
  const { app } = yield lab();
241
- const res = yield (0, supertest_1.default)(app)
241
+ const res = yield (0, loopbackRequest_1.default)(app)
242
242
  .get('/wd/hub/session/s-alice/url')
243
243
  .set({ 'x-xenon-access-key': 'ak-alice', 'x-xenon-token': 'guessed' });
244
244
  yield expectRefused(res);
@@ -248,34 +248,34 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
248
248
  }));
249
249
  it('lets an admin-scoped key drive a session it does not own', () => __awaiter(void 0, void 0, void 0, function* () {
250
250
  const { app, ran } = yield lab();
251
- yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('carol-admin')).expect(200);
251
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('carol-admin')).expect(200);
252
252
  (0, chai_1.expect)(ran).to.deep.equal(['getUrl']);
253
253
  }));
254
254
  it('lets a SUPER_ADMIN drive a session they do not own', () => __awaiter(void 0, void 0, void 0, function* () {
255
255
  const { app, ran } = yield lab();
256
- yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(bearer('root-jwt')).expect(200);
256
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(bearer('root-jwt')).expect(200);
257
257
  (0, chai_1.expect)(ran).to.deep.equal(['getUrl']);
258
258
  }));
259
259
  it("refuses an ADMIN's ordinary key on someone else's session", () => __awaiter(void 0, void 0, void 0, function* () {
260
260
  const { app, ran } = yield lab();
261
- yield expectRefused(yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('carol-ordinary')));
261
+ yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('carol-ordinary')));
262
262
  (0, chai_1.expect)(ran).to.deep.equal([]);
263
263
  }));
264
264
  it('refuses everyone but override admins on a session nobody owns', () => __awaiter(void 0, void 0, void 0, function* () {
265
265
  const { app, ran } = yield lab();
266
- yield expectRefused(yield (0, supertest_1.default)(app).get('/wd/hub/session/s-anon/url').set(as('alice-token')));
267
- yield expectRefused(yield (0, supertest_1.default)(app).get('/wd/hub/session/s-anon/url').set(as('carol-ordinary')));
266
+ yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-anon/url').set(as('alice-token')));
267
+ yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-anon/url').set(as('carol-ordinary')));
268
268
  (0, chai_1.expect)(ran).to.deep.equal([]);
269
- yield (0, supertest_1.default)(app).get('/wd/hub/session/s-anon/url').set(as('carol-admin')).expect(200);
270
- yield (0, supertest_1.default)(app).get('/wd/hub/session/s-anon/url').set(bearer('root-jwt')).expect(200);
269
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-anon/url').set(as('carol-admin')).expect(200);
270
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-anon/url').set(bearer('root-jwt')).expect(200);
271
271
  (0, chai_1.expect)(ran).to.deep.equal(['getUrl', 'getUrl']);
272
272
  }));
273
273
  it('answers a session the caller may not use exactly as one that does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
274
274
  const { app } = yield lab();
275
- const refused = yield (0, supertest_1.default)(app)
275
+ const refused = yield (0, loopbackRequest_1.default)(app)
276
276
  .get('/wd/hub/session/s-alice/url')
277
277
  .set(as('bob-token'));
278
- const missing = yield (0, supertest_1.default)(app)
278
+ const missing = yield (0, loopbackRequest_1.default)(app)
279
279
  .get('/wd/hub/session/no-such-session/url')
280
280
  .set(as('bob-token'));
281
281
  (0, chai_1.expect)(refused.status).to.equal(missing.status);
@@ -293,38 +293,38 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
293
293
  for (const [method, url, name] of commands) {
294
294
  it(`guards ${method.toUpperCase()} ${url.replace('/wd/hub', '')}`, () => __awaiter(void 0, void 0, void 0, function* () {
295
295
  const { app, ran } = yield lab();
296
- yield expectRefused(yield (0, supertest_1.default)(app)[method](url).set(as('bob-token')).send({}));
296
+ yield expectRefused(yield (0, loopbackRequest_1.default)(app)[method](url).set(as('bob-token')).send({}));
297
297
  (0, chai_1.expect)(ran).to.deep.equal([]);
298
- yield (0, supertest_1.default)(app)[method](url).set(as('alice-token')).send({}).expect(200);
298
+ yield (0, loopbackRequest_1.default)(app)[method](url).set(as('alice-token')).send({}).expect(200);
299
299
  (0, chai_1.expect)(ran).to.deep.equal([name]);
300
300
  }));
301
301
  }
302
302
  it('guards a path in another letter case, which the routes also match', () => __awaiter(void 0, void 0, void 0, function* () {
303
303
  const { app, ran } = yield lab();
304
- yield expectRefused(yield (0, supertest_1.default)(app).get('/WD/HUB/SESSION/s-alice/url').set(as('bob-token')));
304
+ yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/WD/HUB/SESSION/s-alice/url').set(as('bob-token')));
305
305
  (0, chai_1.expect)(ran).to.deep.equal([]);
306
306
  }));
307
307
  });
308
308
  describe('what it leaves alone', () => {
309
309
  it('lets createSession through; it has its own credential checks', () => __awaiter(void 0, void 0, void 0, function* () {
310
310
  const { app, ran } = yield lab();
311
- yield (0, supertest_1.default)(app).post('/wd/hub/session').send({ capabilities: {} }).expect(200);
311
+ yield (0, loopbackRequest_1.default)(app).post('/wd/hub/session').send({ capabilities: {} }).expect(200);
312
312
  (0, chai_1.expect)(ran).to.deep.equal(['createSession']);
313
313
  (0, chai_1.expect)(ownerOf.called).to.equal(false);
314
314
  }));
315
315
  it('lets requests that are not session commands through', () => __awaiter(void 0, void 0, void 0, function* () {
316
316
  const { app, ran } = yield lab();
317
- yield (0, supertest_1.default)(app).get('/wd/hub/status').expect(200);
318
- yield (0, supertest_1.default)(app).get('/wd/hub/appium/sessions').expect(200);
319
- yield (0, supertest_1.default)(app).get('/xenon/api/devices').expect(200);
317
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/status').expect(200);
318
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/appium/sessions').expect(200);
319
+ yield (0, loopbackRequest_1.default)(app).get('/xenon/api/devices').expect(200);
320
320
  (0, chai_1.expect)(ran).to.deep.equal(['getStatus', 'getAppiumSessions', 'xenonApi']);
321
321
  (0, chai_1.expect)(verifyKeyPair.called || verifyBearer.called || ownerOf.called).to.equal(false);
322
322
  }));
323
323
  it('does nothing at all when the setting is off (the default)', () => __awaiter(void 0, void 0, void 0, function* () {
324
324
  enabled = false;
325
325
  const { app, ran } = yield lab();
326
- yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').expect(200);
327
- yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token')).expect(200);
326
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').expect(200);
327
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token')).expect(200);
328
328
  (0, chai_1.expect)(ran).to.deep.equal(['getUrl', 'getUrl']);
329
329
  (0, chai_1.expect)(verifyKeyPair.called || verifyBearer.called || ownerOf.called).to.equal(false);
330
330
  (0, chai_1.expect)(warnings).to.deep.equal([]);
@@ -332,7 +332,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
332
332
  it('does nothing when auth is disabled, even with the setting on', () => __awaiter(void 0, void 0, void 0, function* () {
333
333
  authDisabled = true;
334
334
  const { app, ran } = yield lab();
335
- yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').expect(200);
335
+ yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').expect(200);
336
336
  (0, chai_1.expect)(ran).to.deep.equal(['getUrl']);
337
337
  (0, chai_1.expect)(verifyKeyPair.called || verifyBearer.called || ownerOf.called).to.equal(false);
338
338
  }));
@@ -341,7 +341,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
341
341
  it('answers 503 when the owner lookup fails, never letting the command through', () => __awaiter(void 0, void 0, void 0, function* () {
342
342
  ownerOf.rejects(new Error('database is locked'));
343
343
  const { app, ran } = yield lab();
344
- const res = yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('alice-token'));
344
+ const res = yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('alice-token'));
345
345
  (0, chai_1.expect)(res.status).to.equal(503);
346
346
  (0, chai_1.expect)(res.body).to.deep.equal(commandAuth_1.COMMAND_AUTH_UNAVAILABLE_BODY);
347
347
  (0, chai_1.expect)(ran).to.deep.equal([]);
@@ -351,7 +351,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
351
351
  it('answers 503 when the credential check fails', () => __awaiter(void 0, void 0, void 0, function* () {
352
352
  verifyKeyPair.rejects(new Error('database is locked'));
353
353
  const { app, ran } = yield lab();
354
- const res = yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('alice-token'));
354
+ const res = yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('alice-token'));
355
355
  (0, chai_1.expect)(res.status).to.equal(503);
356
356
  (0, chai_1.expect)(res.body).to.deep.equal(commandAuth_1.COMMAND_AUTH_UNAVAILABLE_BODY);
357
357
  (0, chai_1.expect)(ran).to.deep.equal([]);
@@ -360,13 +360,13 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
360
360
  describe('base path', () => {
361
361
  it("normalises the base path the way Appium does ('wd/hub/' → '/wd/hub')", () => __awaiter(void 0, void 0, void 0, function* () {
362
362
  const { app, ran } = yield lab({ basePath: 'wd/hub/' });
363
- yield expectRefused(yield (0, supertest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token')));
363
+ yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token')));
364
364
  (0, chai_1.expect)(ran).to.deep.equal([]);
365
365
  }));
366
366
  it("guards /session/:id under Appium 3's default empty base path", () => __awaiter(void 0, void 0, void 0, function* () {
367
367
  const { app, ran } = yield lab({ basePath: '', routePrefix: '' });
368
- yield expectRefused(yield (0, supertest_1.default)(app).get('/session/s-alice/url').set(as('bob-token')));
369
- yield (0, supertest_1.default)(app).post('/session').send({}).expect(200);
368
+ yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/session/s-alice/url').set(as('bob-token')));
369
+ yield (0, loopbackRequest_1.default)(app).post('/session').send({}).expect(200);
370
370
  (0, chai_1.expect)(ran).to.deep.equal(['createSession']);
371
371
  }));
372
372
  });