@xenon-device-management/xenon 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/README.md +2 -1
  2. package/lib/package.json +3 -4
  3. package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-XpewX5nn.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-BQDv3ZCC.js} +1 -1
  5. package/lib/public/assets/BugReportButton-C7OmS61B.js +10 -0
  6. package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-wl3l2Zlt.js} +1 -1
  7. package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-fPxujqqE.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-5bCutWrs.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-owsBiDT1.js} +1 -1
  10. package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-DFhqzPSt.js} +1 -1
  11. package/lib/public/assets/{Modal-CceoMY7D.js → Modal-Bw-a-E-e.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-WtByf5bC.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-FpWWFNgT.js} +1 -1
  14. package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-Jzn9c7o8.js} +1 -1
  15. package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-BR7nOhpT.js} +1 -1
  16. package/lib/public/assets/{Table-LV7zFyZg.js → Table-B_Va3V05.js} +1 -1
  17. package/lib/public/assets/{activity-D17TUloi.js → activity-DxRFWvkN.js} +1 -1
  18. package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-CQf-9_V6.js} +1 -1
  19. package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-SoE1Kd7N.js} +1 -1
  20. package/lib/public/assets/{apps-Dbnzd0fW.js → apps-CgGXTlgn.js} +1 -1
  21. package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-uQ6WIwiC.js} +1 -1
  22. package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-C9RyfzXS.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-DFHi_8yG.js} +1 -1
  24. package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-Bii1t4qc.js} +2 -2
  25. package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-Bnv8Z9hi.js} +1 -1
  26. package/lib/public/assets/{button-xtAhYR-f.js → button-_Y78wXiP.js} +1 -1
  27. package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-D4MqSOc2.js} +1 -1
  28. package/lib/public/assets/{check-CyuEoKkd.js → check-uGHCXe-L.js} +1 -1
  29. package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-Dy1OqpzB.js} +1 -1
  30. package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-BUsldEli.js} +1 -1
  31. package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-D3Qq0tKp.js} +1 -1
  32. package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-B4IOD1DE.js} +1 -1
  33. package/lib/public/assets/{clock-Cd-t7xpe.js → clock-BfYfzvSg.js} +1 -1
  34. package/lib/public/assets/{copy-C6zerX3I.js → copy-DZwYE5Va.js} +1 -1
  35. package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CRG4Utt5.js} +3 -3
  36. package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
  37. package/lib/public/assets/{download-x9WP7mcL.js → download-CCDYo_TN.js} +1 -1
  38. package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-DuLCmVFb.js} +1 -1
  39. package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-CtKNPqzb.js} +1 -1
  40. package/lib/public/assets/{index-BR1wTIPC.js → index-3uxl3o7K.js} +43 -43
  41. package/lib/public/assets/{index-BvH_ekRy.js → index-Zy1Op5C1.js} +1 -1
  42. package/lib/public/assets/{input-DejcCiTI.js → input-CzTWvUXZ.js} +1 -1
  43. package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CI-nYDFM.js} +1 -1
  44. package/lib/public/assets/{layers-BKqYLiHE.js → layers-YZZGFyTX.js} +1 -1
  45. package/lib/public/assets/{lock-LFsFhc17.js → lock-L8tzWgDo.js} +1 -1
  46. package/lib/public/assets/{login-x2pkzt-t.js → login-DBbX-fu8.js} +1 -1
  47. package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-AJwkOIsE.js} +1 -1
  48. package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-Di_66F2m.js} +1 -1
  49. package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-Cnab2zdg.js} +1 -1
  50. package/lib/public/assets/{network-Z_zBhpsD.js → network-DiSumY-1.js} +1 -1
  51. package/lib/public/assets/{overview-BY5uv08S.js → overview-CwE4gXl5.js} +1 -1
  52. package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CfR4Sm9V.js} +1 -1
  53. package/lib/public/assets/{plus-BTD0dnQ0.js → plus-DOS8P9CQ.js} +1 -1
  54. package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-BYpHwQIH.js} +1 -1
  55. package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BRVJU_qV.js} +1 -1
  56. package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Bi3JvAFZ.js} +1 -1
  57. package/lib/public/assets/{select-Uc9E5pye.js → select-Cqx7pEou.js} +1 -1
  58. package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C0DsncKj.js} +1 -1
  59. package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CAhpdilF.js} +1 -1
  60. package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CrCVeW2m.js} +1 -1
  61. package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-F_hwYqEt.js} +1 -1
  62. package/lib/public/assets/{settings-CkSWiZq8.js → settings-C-s5pypL.js} +1 -1
  63. package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-DY5uzv6A.js} +1 -1
  64. package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-Dli7gwyg.js} +1 -1
  65. package/lib/public/assets/{teams-PpoEQz31.js → teams-ui4IhHNC.js} +1 -1
  66. package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV4hSmXJ.js} +1 -1
  67. package/lib/public/assets/{upload-C3i7sAZl.js → upload-DkGy_J-L.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-BFtMqQOh.js} +1 -1
  69. package/lib/public/assets/{users-Drd7ifJx.js → users-BU0nXIVe.js} +1 -1
  70. package/lib/public/assets/{users-CEJ3fC78.js → users-Nb_rzRmF.js} +1 -1
  71. package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-CUNzD2kH.js} +1 -1
  72. package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-Ca3UFXZe.js} +1 -1
  73. package/lib/public/assets/{zap-4yN8Jdfw.js → zap-B2bH2vKX.js} +1 -1
  74. package/lib/public/index.html +1 -1
  75. package/lib/src/InternalHttpClient.js +5 -1
  76. package/lib/src/XenonCapabilityManager.js +15 -1
  77. package/lib/src/app/appiumBasePath.js +32 -0
  78. package/lib/src/app/index.js +12 -0
  79. package/lib/src/app/insertBeforeRoutes.js +24 -4
  80. package/lib/src/app/registerCommandAuth.js +152 -33
  81. package/lib/src/app/routers/control.js +11 -104
  82. package/lib/src/app/routers/grid.js +28 -8
  83. package/lib/src/app/routers/nodePhoneControl.js +301 -0
  84. package/lib/src/app/ws/h264StreamWs.js +43 -12
  85. package/lib/src/app/ws/logcatWs.js +48 -14
  86. package/lib/src/app/ws/nodeSocketRelay.js +167 -0
  87. package/lib/src/app/ws/upgradeRouter.js +215 -0
  88. package/lib/src/dashboard/event-manager.js +7 -6
  89. package/lib/src/data-service/device-service.js +152 -49
  90. package/lib/src/data-service/device-store.js +92 -10
  91. package/lib/src/data-service/deviceClaims.js +116 -0
  92. package/lib/src/data-service/deviceFieldOwners.js +32 -10
  93. package/lib/src/data-service/prisma-store.js +102 -31
  94. package/lib/src/device-managers/AndroidDeviceManager.js +40 -42
  95. package/lib/src/device-managers/HealthMonitorService.js +17 -2
  96. package/lib/src/device-managers/IOSDeviceManager.js +5 -1
  97. package/lib/src/device-managers/NodeDevices.js +7 -2
  98. package/lib/src/device-managers/android/AndroidH264StreamService.js +13 -5
  99. package/lib/src/device-managers/android/AndroidStreamService.js +9 -6
  100. package/lib/src/device-managers/android/previewHold.js +2 -2
  101. package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
  102. package/lib/src/device-managers/ios/IOSStreamService.js +162 -40
  103. package/lib/src/device-managers/localDeviceHosts.js +86 -0
  104. package/lib/src/device-managers/ownDeviceRow.js +38 -0
  105. package/lib/src/device-utils.js +78 -24
  106. package/lib/src/gateway/createHandoff.js +73 -0
  107. package/lib/src/gateway/defaultGateway.js +80 -0
  108. package/lib/src/gateway/forwardToNode.js +147 -0
  109. package/lib/src/gateway/hubSessionToken.js +356 -0
  110. package/lib/src/gateway/internalCall.js +90 -0
  111. package/lib/src/gateway/nodeSessionStatus.js +140 -0
  112. package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
  113. package/lib/src/gateway/sessionCreate.js +136 -0
  114. package/lib/src/gateway/sessionGateway.js +201 -0
  115. package/lib/src/gateway/sessionLocator.js +100 -0
  116. package/lib/src/generated/client/edge.js +7 -4
  117. package/lib/src/generated/client/index-browser.js +4 -1
  118. package/lib/src/generated/client/index.d.ts +112 -1
  119. package/lib/src/generated/client/index.js +7 -4
  120. package/lib/src/generated/client/package.json +1 -1
  121. package/lib/src/generated/client/schema.prisma +9 -0
  122. package/lib/src/generated/client/wasm.js +4 -1
  123. package/lib/src/helpers/index.js +5 -1
  124. package/lib/src/logger.js +5 -0
  125. package/lib/src/middleware/authMiddleware.js +80 -0
  126. package/lib/src/middleware/commandAuth.js +31 -17
  127. package/lib/src/middleware/csrfMiddleware.js +3 -2
  128. package/lib/src/middleware/deviceAccessGuard.js +4 -1
  129. package/lib/src/middleware/sessionListingFilter.js +153 -0
  130. package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
  131. package/lib/src/plugin.js +27 -1
  132. package/lib/src/prisma.js +2 -1
  133. package/lib/src/services/DeviceReconciler.js +5 -2
  134. package/lib/src/services/OrphanSweeper.js +12 -19
  135. package/lib/src/services/PortAllocator.js +75 -16
  136. package/lib/src/services/ServerManager.js +63 -30
  137. package/lib/src/services/SessionLifecycleService.js +335 -76
  138. package/lib/src/services/ShutdownCoordinator.js +7 -1
  139. package/lib/src/services/SocketServer.js +11 -2
  140. package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
  141. package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
  142. package/lib/src/services/device-access/ticketActorAccess.js +3 -1
  143. package/lib/src/services/lease/LeaseOrphanSweeper.js +34 -6
  144. package/lib/src/services/lease/LeaseService.js +19 -5
  145. package/lib/src/services/session/nodeCreateCaps.js +55 -0
  146. package/lib/src/services/session/sessionCredentials.js +7 -59
  147. package/lib/src/sessions/CloudSession.js +12 -0
  148. package/lib/src/sessions/LocalSession.js +52 -26
  149. package/lib/src/sessions/RemoteSession.js +116 -9
  150. package/lib/src/sessions/SessionManager.js +49 -6
  151. package/lib/src/sessions/appiumUmbrella.js +32 -0
  152. package/lib/test/helpers/appium-umbrella.js +84 -0
  153. package/lib/test/helpers/fake-device-table.js +97 -0
  154. package/lib/test/helpers/loki-stores.js +41 -0
  155. package/lib/test/helpers/loopbackRequest.js +77 -0
  156. package/lib/test/helpers/own-node-id.js +29 -0
  157. package/lib/test/helpers/scratch-database.js +100 -0
  158. package/lib/test/helpers/scratch-port-leases.js +119 -0
  159. package/lib/test/helpers/test-container.js +35 -27
  160. package/lib/test/integration/androidDevices.spec.js +5 -4
  161. package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
  162. package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
  163. package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
  164. package/lib/test/setup/scratch-default-database.js +32 -0
  165. package/lib/test/unit/AndroidDeviceManager.spec.js +56 -1
  166. package/lib/test/unit/DeviceModel.spec.js +9 -9
  167. package/lib/test/unit/HealingCascade.test.js +5 -1
  168. package/lib/test/unit/OrphanSweeper.test.js +17 -13
  169. package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
  170. package/lib/test/unit/StreamTicketService.spec.js +4 -1
  171. package/lib/test/unit/TokenEndpoint.spec.js +17 -3
  172. package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
  173. package/lib/test/unit/android-h264-service.spec.js +24 -0
  174. package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
  175. package/lib/test/unit/apps-team-visibility.spec.js +16 -16
  176. package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
  177. package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
  178. package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
  179. package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
  180. package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
  181. package/lib/test/unit/authMiddleware.test.js +3 -0
  182. package/lib/test/unit/bug-report/route.spec.js +9 -9
  183. package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
  184. package/lib/test/unit/clipboard-route.spec.js +22 -8
  185. package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
  186. package/lib/test/unit/command-auth.spec.js +31 -31
  187. package/lib/test/unit/control-device-lookup.spec.js +9 -9
  188. package/lib/test/unit/control-forward-decision.spec.js +115 -0
  189. package/lib/test/unit/control-lookup-failure.spec.js +3 -3
  190. package/lib/test/unit/create-error-message.spec.js +43 -0
  191. package/lib/test/unit/create-handoff.spec.js +86 -0
  192. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  193. package/lib/test/unit/device-access-guard.spec.js +66 -28
  194. package/lib/test/unit/device-service.spec.js +4 -0
  195. package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
  196. package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
  197. package/lib/test/unit/device-team-guard.spec.js +20 -20
  198. package/lib/test/unit/device-utils.spec.js +8 -0
  199. package/lib/test/unit/extract-team-cap.spec.js +65 -0
  200. package/lib/test/unit/grid-admin-only.spec.js +3 -3
  201. package/lib/test/unit/healing-tier-gate.spec.js +5 -1
  202. package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
  203. package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
  204. package/lib/test/unit/helpers.spec.js +27 -0
  205. package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
  206. package/lib/test/unit/hub-create-once.spec.js +190 -0
  207. package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
  208. package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
  209. package/lib/test/unit/hub-node-control-forward.spec.js +608 -0
  210. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
  211. package/lib/test/unit/hub-node-race.spec.js +426 -0
  212. package/lib/test/unit/hub-node-same-host.spec.js +356 -0
  213. package/lib/test/unit/hub-register-removal.spec.js +165 -0
  214. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
  215. package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
  216. package/lib/test/unit/hub-session-token.spec.js +265 -0
  217. package/lib/test/unit/insert-before-routes.spec.js +5 -5
  218. package/lib/test/unit/install-repository-app-team.spec.js +6 -2
  219. package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
  220. package/lib/test/unit/internal-call.spec.js +181 -0
  221. package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
  222. package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
  223. package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
  224. package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
  225. package/lib/test/unit/is-xenon-running.spec.js +69 -0
  226. package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
  227. package/lib/test/unit/lease/LeaseService.spec.js +28 -1
  228. package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
  229. package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +107 -0
  230. package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
  231. package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
  232. package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
  233. package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
  234. package/lib/test/unit/logcat-ws.spec.js +12 -1
  235. package/lib/test/unit/loopback-request.spec.js +81 -0
  236. package/lib/test/unit/mark-node-busy-loki.spec.js +53 -0
  237. package/lib/test/unit/node-create-caps.spec.js +54 -0
  238. package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
  239. package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
  240. package/lib/test/unit/node-phone-control.spec.js +131 -0
  241. package/lib/test/unit/node-socket-relay.spec.js +357 -0
  242. package/lib/test/unit/omni-vision.spec.js +7 -3
  243. package/lib/test/unit/plugin.spec.js +45 -4
  244. package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
  245. package/lib/test/unit/profile-router.test.js +11 -11
  246. package/lib/test/unit/queue-requester.spec.js +5 -0
  247. package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
  248. package/lib/test/unit/queue-visibility.spec.js +1 -1
  249. package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
  250. package/lib/test/unit/recording-instrumentation.spec.js +5 -0
  251. package/lib/test/unit/recording-source-ended.spec.js +5 -0
  252. package/lib/test/unit/recording-store.spec.js +9 -3
  253. package/lib/test/unit/recordings-library-routes.spec.js +51 -51
  254. package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
  255. package/lib/test/unit/remote-session-create.spec.js +130 -0
  256. package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
  257. package/lib/test/unit/reset-link.test.js +14 -14
  258. package/lib/test/unit/session-app-resolution.spec.js +14 -10
  259. package/lib/test/unit/session-assets.spec.js +9 -9
  260. package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
  261. package/lib/test/unit/session-create-layer.spec.js +255 -0
  262. package/lib/test/unit/session-credential-strip.spec.js +25 -9
  263. package/lib/test/unit/session-credentials.spec.js +0 -47
  264. package/lib/test/unit/session-listing-auth.spec.js +345 -0
  265. package/lib/test/unit/session-locator.spec.js +250 -0
  266. package/lib/test/unit/session-node-calls.spec.js +174 -0
  267. package/lib/test/unit/session-owner-resolver.spec.js +107 -0
  268. package/lib/test/unit/session-recovery-routing.spec.js +118 -0
  269. package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
  270. package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
  271. package/lib/test/unit/socketServer.bearer.spec.js +3 -1
  272. package/lib/test/unit/stream-leave-route.spec.js +19 -7
  273. package/lib/test/unit/stream-own-device-row.spec.js +164 -0
  274. package/lib/test/unit/stream-stop-ownership.spec.js +17 -5
  275. package/lib/test/unit/stream-stop-recording.spec.js +17 -5
  276. package/lib/test/unit/stream-ticket-identity.spec.js +26 -6
  277. package/lib/test/unit/stream-watchdog-timers.spec.js +46 -0
  278. package/lib/test/unit/upgrade-router.spec.js +446 -0
  279. package/lib/test/unit/users-router.test.js +11 -8
  280. package/lib/test/unit/verify-credential.spec.js +4 -1
  281. package/lib/test/unit/webdriver-info-route.spec.js +64 -0
  282. package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
  283. package/lib/tsconfig.tsbuildinfo +1 -1
  284. package/package.json +3 -4
  285. package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
  286. package/prisma/schema.prisma +9 -0
  287. package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
  288. package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
  289. package/lib/src/proxy/wd-command-proxy.js +0 -221
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
+ return new (P || (P = Promise))(function (resolve, reject) {
11
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
15
+ });
16
+ };
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.NodeBasePathResolver = exports.LOOKUP_TIMEOUT_MS = exports.FALLBACK_TTL_MS = exports.BASE_PATH_TTL_MS = exports.WEBDRIVER_INFO_PATH = void 0;
22
+ exports.webdriverInfo = webdriverInfo;
23
+ exports.webdriverInfoHandler = webdriverInfoHandler;
24
+ exports.originOf = originOf;
25
+ exports.nodeWebDriverUrl = nodeWebDriverUrl;
26
+ const https_1 = __importDefault(require("https"));
27
+ const axios_1 = __importDefault(require("axios"));
28
+ const typedi_1 = require("typedi");
29
+ const logger_1 = __importDefault(require("../logger"));
30
+ const helpers_1 = require("../helpers");
31
+ const appiumBasePath_1 = require("../app/appiumBasePath");
32
+ const PluginContext_1 = require("../PluginContext");
33
+ /**
34
+ * Where a node's WebDriver API lives.
35
+ *
36
+ * A phone's `host` column is the node's origin (`http://<ip>:<port>`), never
37
+ * its Appium base path, and a hub and its nodes need not share one. So each
38
+ * Xenon server says what its base path is at `GET /xenon/api/webdriver`,
39
+ * which needs no login (the path is part of every client's URL anyway), and
40
+ * the hub asks a node before it sends that node a session or a command.
41
+ *
42
+ * The answer is kept for a minute. A node that does not answer it (an older
43
+ * Xenon, or one that is down) is taken to share the hub's own base path, the
44
+ * one rule that held before, and asked again after a few seconds.
45
+ */
46
+ exports.WEBDRIVER_INFO_PATH = '/xenon/api/webdriver';
47
+ exports.BASE_PATH_TTL_MS = 60000;
48
+ exports.FALLBACK_TTL_MS = 10000;
49
+ exports.LOOKUP_TIMEOUT_MS = 3000;
50
+ /** What a server answers at WEBDRIVER_INFO_PATH. */
51
+ function webdriverInfo(basePath) {
52
+ return { basePath: (0, appiumBasePath_1.normalizeBasePath)(basePath) };
53
+ }
54
+ function webdriverInfoHandler(basePath) {
55
+ return (_req, res) => {
56
+ res.json(webdriverInfo(basePath()));
57
+ };
58
+ }
59
+ /** A phone host's origin: scheme, host and port, without a path or trailing slash. */
60
+ function originOf(host) {
61
+ try {
62
+ return new URL(host).origin;
63
+ }
64
+ catch (_a) {
65
+ return host.replace(/\/+$/, '');
66
+ }
67
+ }
68
+ let NodeBasePathResolver = class NodeBasePathResolver {
69
+ constructor() {
70
+ this.logger = logger_1.default.scope('NodeBasePath');
71
+ this.cache = new Map();
72
+ this.pending = new Map();
73
+ this.now = () => Date.now();
74
+ }
75
+ /** The node's base path, or `fallback` when the node does not say. */
76
+ resolve(origin, fallback) {
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ const hit = this.cache.get(origin);
79
+ if (hit && hit.expiresAt > this.now())
80
+ return hit.basePath;
81
+ const inFlight = this.pending.get(origin);
82
+ if (inFlight)
83
+ return inFlight;
84
+ const lookup = this.lookUp(origin, (0, appiumBasePath_1.normalizeBasePath)(fallback)).finally(() => this.pending.delete(origin));
85
+ this.pending.set(origin, lookup);
86
+ return lookup;
87
+ });
88
+ }
89
+ forget(origin) {
90
+ this.cache.delete(origin);
91
+ }
92
+ lookUp(origin, fallback) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ var _a, _b, _c;
95
+ let advertised;
96
+ try {
97
+ const rejectUnauthorized = (_a = typedi_1.Container.get(PluginContext_1.PluginContext).pluginArgs) === null || _a === void 0 ? void 0 : _a.tlsRejectUnauthorized;
98
+ const response = yield axios_1.default.get(`${origin}${exports.WEBDRIVER_INFO_PATH}`, {
99
+ timeout: exports.LOOKUP_TIMEOUT_MS,
100
+ validateStatus: (status) => status === 200,
101
+ httpsAgent: new https_1.default.Agent({ rejectUnauthorized: rejectUnauthorized !== false }),
102
+ });
103
+ const basePath = (_b = response.data) === null || _b === void 0 ? void 0 : _b.basePath;
104
+ if (typeof basePath === 'string')
105
+ advertised = (0, appiumBasePath_1.normalizeBasePath)(basePath);
106
+ }
107
+ catch (err) {
108
+ this.logger.debug(`${origin} did not say its base path (${(_c = err === null || err === void 0 ? void 0 : err.message) !== null && _c !== void 0 ? _c : err})`);
109
+ }
110
+ if (advertised === undefined) {
111
+ this.logger.info(`Node ${origin} did not report its WebDriver base path; using this hub's ('${fallback}').`);
112
+ }
113
+ const basePath = advertised !== null && advertised !== void 0 ? advertised : fallback;
114
+ this.cache.set(origin, {
115
+ basePath,
116
+ expiresAt: this.now() + (advertised === undefined ? exports.FALLBACK_TTL_MS : exports.BASE_PATH_TTL_MS),
117
+ });
118
+ return basePath;
119
+ });
120
+ }
121
+ };
122
+ exports.NodeBasePathResolver = NodeBasePathResolver;
123
+ exports.NodeBasePathResolver = NodeBasePathResolver = __decorate([
124
+ (0, typedi_1.Service)()
125
+ ], NodeBasePathResolver);
126
+ /**
127
+ * The WebDriver base URL of the server that drives a phone: a cloud
128
+ * provider's own URL, or a node's origin plus its own base path.
129
+ * `fallbackBasePath` is this hub's, for a node that does not say.
130
+ */
131
+ function nodeWebDriverUrl(device, fallbackBasePath) {
132
+ return __awaiter(this, void 0, void 0, function* () {
133
+ if (device.cloud)
134
+ return (0, helpers_1.nodeUrl)(device, fallbackBasePath);
135
+ const origin = originOf(device.host);
136
+ const basePath = yield typedi_1.Container.get(NodeBasePathResolver).resolve(origin, fallbackBasePath);
137
+ return `${origin}${basePath}`;
138
+ });
139
+ }
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createSessionCreateLayer = createSessionCreateLayer;
13
+ const base_driver_1 = require("@appium/base-driver");
14
+ const commandAuth_1 = require("../middleware/commandAuth");
15
+ const createHandoff_1 = require("./createHandoff");
16
+ const hubSessionToken_1 = require("./hubSessionToken");
17
+ function isPlainObject(value) {
18
+ return !!value && typeof value === 'object' && !Array.isArray(value);
19
+ }
20
+ /** The path under the layer's mount point is the create path itself. */
21
+ function isCreatePath(url) {
22
+ const path = (url || '/').split('?')[0];
23
+ return path === '/' || path === '';
24
+ }
25
+ /** Answer an error as Appium's route answers it. */
26
+ function sendError(res, error) {
27
+ if (res.headersSent)
28
+ return;
29
+ const err = error instanceof Error ? error : new Error(String(error));
30
+ const [status, body] = (0, base_driver_1.getResponseForW3CError)(err);
31
+ res.status(status).json(body);
32
+ }
33
+ /** Appium's answer to a new session: `{ value: { capabilities, sessionId } }`. */
34
+ function newSessionBody(created) {
35
+ var _a;
36
+ const value = created === null || created === void 0 ? void 0 : created.value;
37
+ if (!Array.isArray(value) || typeof value[0] !== 'string') {
38
+ throw new base_driver_1.errors.SessionNotCreatedError('The node answered the create without a session id.');
39
+ }
40
+ return { value: { capabilities: (_a = value[1]) !== null && _a !== void 0 ? _a : null, sessionId: value[0] } };
41
+ }
42
+ function createSessionCreateLayer(deps) {
43
+ const { logger } = deps;
44
+ /** The grant a node creates by, or false when the request has been answered. */
45
+ function hubGrantOf(token, res) {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ if (token === undefined || !deps.hubGrants || deps.authDisabled())
48
+ return undefined;
49
+ let grant;
50
+ try {
51
+ grant = yield deps.hubGrants.verifyCreate(token);
52
+ }
53
+ catch (error) {
54
+ logger.error(`New session refused: the hub's token could not be checked: ${(0, commandAuth_1.summarize)(error)}`);
55
+ res.status(commandAuth_1.COMMAND_AUTH_UNAVAILABLE_STATUS).json(commandAuth_1.COMMAND_AUTH_UNAVAILABLE_BODY);
56
+ return false;
57
+ }
58
+ if (grant)
59
+ return grant;
60
+ logger.warn("New session refused: the hub's token on it is not valid");
61
+ sendError(res, new base_driver_1.errors.InvalidArgumentError("session rejected: the hub's token for this session is not valid " +
62
+ '(forged, expired, already used, or not a create token)'));
63
+ return false;
64
+ });
65
+ }
66
+ function create(res, next, caps, token) {
67
+ return __awaiter(this, void 0, void 0, function* () {
68
+ // Registered before anything waits, so a client that leaves while its
69
+ // phone is being found is seen.
70
+ const request = {
71
+ closed: false,
72
+ };
73
+ res.on('close', () => {
74
+ var _a;
75
+ request.closed = true;
76
+ (_a = request.handoff) === null || _a === void 0 ? void 0 : _a.abandon();
77
+ });
78
+ const hubGrant = yield hubGrantOf(token, res);
79
+ if (hubGrant === false)
80
+ return;
81
+ const lifecycle = deps.lifecycle();
82
+ let allocation;
83
+ try {
84
+ allocation = yield lifecycle.prepareSession(caps, hubGrant ? { hubGrant } : {});
85
+ }
86
+ catch (error) {
87
+ logger.warn(`New session refused: ${(0, commandAuth_1.summarize)(error)}`);
88
+ sendError(res, error);
89
+ return;
90
+ }
91
+ if (request.closed) {
92
+ logger.warn('The client left before its session was created; its device was released.');
93
+ yield lifecycle.releaseAllocation(allocation);
94
+ return;
95
+ }
96
+ if (allocation.remote) {
97
+ try {
98
+ res.status(200).json(newSessionBody(yield lifecycle.completeRemoteSession(allocation)));
99
+ }
100
+ catch (error) {
101
+ sendError(res, error);
102
+ }
103
+ return;
104
+ }
105
+ const local = new createHandoff_1.CreateHandoff(allocation, (unused) => {
106
+ logger.warn("A new session's device was allocated, but the request ended without Xenon's " +
107
+ 'createSession taking it; the device was released.');
108
+ lifecycle
109
+ .releaseAllocation(unused)
110
+ .catch((error) => logger.error(`Could not release a device: ${(0, commandAuth_1.summarize)(error)}`));
111
+ });
112
+ request.handoff = local;
113
+ (0, createHandoff_1.runWithCreateHandoff)(local, () => next());
114
+ });
115
+ }
116
+ return function xenonSessionCreate(req, res, next) {
117
+ if (req.method !== 'POST' || !isCreatePath(req.url))
118
+ return next();
119
+ // The hub's token is for this layer alone: nothing after it sees it.
120
+ const presented = req.headers[hubSessionToken_1.HUB_TOKEN_HEADER];
121
+ const token = typeof presented === 'string' ? presented : undefined;
122
+ delete req.headers[hubSessionToken_1.HUB_TOKEN_HEADER];
123
+ // Anything but a capabilities object is Appium's to answer, as before.
124
+ const caps = isPlainObject(req.body) ? req.body.capabilities : undefined;
125
+ if (!isPlainObject(caps))
126
+ return next();
127
+ // W3C lets clients omit `firstMatch` (it defaults to [{}]); what reads the
128
+ // caps from here on relies on `firstMatch[0]`, as the plugin did.
129
+ if (!Array.isArray(caps.firstMatch) || caps.firstMatch.length === 0)
130
+ caps.firstMatch = [{}];
131
+ create(res, next, caps, token).catch((error) => {
132
+ logger.error(`Session gateway: a create failed: ${(0, commandAuth_1.summarize)(error)}`);
133
+ sendError(res, error);
134
+ });
135
+ };
136
+ }
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ROUTING_UNAVAILABLE_BODY = exports.ROUTING_UNAVAILABLE_STATUS = void 0;
13
+ exports.createSessionGatewayLayer = createSessionGatewayLayer;
14
+ exports.createHubRouting = createHubRouting;
15
+ const commandAuth_1 = require("../middleware/commandAuth");
16
+ const internalCall_1 = require("./internalCall");
17
+ const hubSessionToken_1 = require("./hubSessionToken");
18
+ const forwardToNode_1 = require("./forwardToNode");
19
+ /** Where a session runs could not be determined (a database error). */
20
+ exports.ROUTING_UNAVAILABLE_STATUS = 503;
21
+ exports.ROUTING_UNAVAILABLE_BODY = {
22
+ value: {
23
+ error: 'unknown error',
24
+ message: 'Xenon could not find where this session runs. Try again.',
25
+ stacktrace: '',
26
+ },
27
+ };
28
+ function headerValue(req, name) {
29
+ const value = req.headers[name];
30
+ return typeof value === 'string' ? value : undefined;
31
+ }
32
+ function where(req) {
33
+ var _a;
34
+ return `${req.method} ${String((_a = req.originalUrl) !== null && _a !== void 0 ? _a : req.url).split('?')[0]}`;
35
+ }
36
+ function createSessionGatewayLayer(deps) {
37
+ const authorize = (0, commandAuth_1.createCommandAuthMiddleware)(deps.auth);
38
+ const logger = deps.auth.logger;
39
+ const authActive = () => deps.auth.enabled() && !deps.auth.authDisabled();
40
+ function route(req, res, next, sessionId) {
41
+ return __awaiter(this, void 0, void 0, function* () {
42
+ var _a, _b;
43
+ // A request that came from a hub is never forwarded again: a hub that
44
+ // routed a session to itself would otherwise loop.
45
+ const fromHub = headerValue(req, hubSessionToken_1.HUB_TOKEN_HEADER) !== undefined;
46
+ delete req.headers[hubSessionToken_1.HUB_TOKEN_HEADER];
47
+ if (deps.routing && !fromHub) {
48
+ let location;
49
+ try {
50
+ location = yield deps.routing.locate(sessionId);
51
+ }
52
+ catch (error) {
53
+ logger.error(`Could not find where session ${sessionId} runs for ${where(req)}: ${(0, commandAuth_1.summarize)(error)}`);
54
+ res.status(exports.ROUTING_UNAVAILABLE_STATUS).json(exports.ROUTING_UNAVAILABLE_BODY);
55
+ return;
56
+ }
57
+ if (location.kind === 'remote') {
58
+ yield deps.routing.forward(req, res, sessionId, location);
59
+ return;
60
+ }
61
+ }
62
+ const latency = (_b = (_a = deps.latencyOf) === null || _a === void 0 ? void 0 : _a.call(deps, sessionId)) !== null && _b !== void 0 ? _b : 0;
63
+ if (latency > 0)
64
+ yield new Promise((resolve) => setTimeout(resolve, latency));
65
+ next();
66
+ });
67
+ }
68
+ return function xenonSessionGateway(req, res, next) {
69
+ var _a, _b;
70
+ const sessionId = String((_b = (_a = req.params) === null || _a === void 0 ? void 0 : _a.sessionId) !== null && _b !== void 0 ? _b : '');
71
+ const proceed = () => {
72
+ route(req, res, next, sessionId).catch((error) => {
73
+ logger.error(`Session gateway failed for ${where(req)}: ${(0, commandAuth_1.summarize)(error)}`);
74
+ if (!res.headersSent)
75
+ res.status(forwardToNode_1.NODE_UNREACHABLE_STATUS).json(forwardToNode_1.NODE_UNREACHABLE_BODY);
76
+ });
77
+ };
78
+ if ((0, internalCall_1.isInternalCall)(req))
79
+ return proceed();
80
+ const hubToken = headerValue(req, hubSessionToken_1.HUB_TOKEN_HEADER);
81
+ if (hubToken !== undefined && deps.hubTokens && authActive()) {
82
+ deps.hubTokens
83
+ .verify(hubToken, sessionId)
84
+ .then((valid) => {
85
+ if (valid)
86
+ return proceed();
87
+ logger.warn(`Command refused: ${where(req)} (session ${sessionId}): hub token not valid for this session`);
88
+ res.status(commandAuth_1.UNKNOWN_SESSION_STATUS).json(commandAuth_1.UNKNOWN_SESSION_BODY);
89
+ })
90
+ .catch((error) => {
91
+ logger.error(`Command auth unavailable for ${where(req)} (session ${sessionId}): ` +
92
+ `hub token check failed: ${(0, commandAuth_1.summarize)(error)}`);
93
+ if (!res.headersSent) {
94
+ res.status(commandAuth_1.COMMAND_AUTH_UNAVAILABLE_STATUS).json(commandAuth_1.COMMAND_AUTH_UNAVAILABLE_BODY);
95
+ }
96
+ });
97
+ return;
98
+ }
99
+ authorize(req, res, proceed);
100
+ };
101
+ }
102
+ const BODYLESS = new Set(['GET', 'HEAD', 'DELETE', 'OPTIONS']);
103
+ /** The part of the path after `/session/<id>`, with its query string, from the mounted layer. */
104
+ function restOf(req) {
105
+ const url = req.url || '/';
106
+ const q = url.indexOf('?');
107
+ const pathname = q === -1 ? url : url.slice(0, q);
108
+ const query = q === -1 ? '' : url.slice(q);
109
+ return `${pathname === '/' ? '' : pathname}${query}`;
110
+ }
111
+ function createHubRouting(deps) {
112
+ const { logger } = deps;
113
+ function nodeRequest(req, sessionId, location, signal) {
114
+ return __awaiter(this, void 0, void 0, function* () {
115
+ var _a;
116
+ const headers = (0, forwardToNode_1.forwardedRequestHeaders)(req.headers);
117
+ if (!location.cloud) {
118
+ const token = yield deps.tokenFor(sessionId);
119
+ if (token)
120
+ headers[hubSessionToken_1.HUB_TOKEN_HEADER] = token;
121
+ }
122
+ let body;
123
+ if (!BODYLESS.has(req.method.toUpperCase())) {
124
+ body = Buffer.from(JSON.stringify((_a = req.body) !== null && _a !== void 0 ? _a : {}));
125
+ headers['content-type'] = 'application/json; charset=utf-8';
126
+ }
127
+ return {
128
+ url: `${location.url}/session/${encodeURIComponent(sessionId)}${restOf(req)}`,
129
+ method: req.method,
130
+ headers,
131
+ body,
132
+ signal,
133
+ };
134
+ });
135
+ }
136
+ function deleteRemote(req, res, sessionId, location) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ let answer;
139
+ yield deps.deleteSession(() => __awaiter(this, void 0, void 0, function* () {
140
+ answer = yield (0, forwardToNode_1.readAnswer)(yield (0, forwardToNode_1.sendToNode)(yield nodeRequest(req, sessionId, location)));
141
+ return answer;
142
+ }), sessionId);
143
+ deps.forget(sessionId);
144
+ deps.forgetToken(sessionId);
145
+ if (answer) {
146
+ (0, forwardToNode_1.sendAnswer)(res, answer);
147
+ }
148
+ else {
149
+ logger.warn(`Session ${sessionId}: the node did not answer its DELETE; ended here anyway.`);
150
+ res.status(forwardToNode_1.NODE_UNREACHABLE_STATUS).json(forwardToNode_1.NODE_UNREACHABLE_BODY);
151
+ }
152
+ });
153
+ }
154
+ return {
155
+ locate: (sessionId) => deps.locate(sessionId),
156
+ forward(req, res, sessionId, location) {
157
+ return __awaiter(this, void 0, void 0, function* () {
158
+ const rest = restOf(req);
159
+ if (req.method.toUpperCase() === 'DELETE' && (rest === '' || rest.startsWith('?'))) {
160
+ yield deleteRemote(req, res, sessionId, location);
161
+ return;
162
+ }
163
+ yield deps
164
+ .touch(sessionId)
165
+ .catch((error) => logger.warn(`Could not record activity for session ${sessionId}: ${(0, commandAuth_1.summarize)(error)}`));
166
+ const hooks = location.session && !location.cloud ? deps.dashboard : undefined;
167
+ const command = hooks
168
+ ? deps.commandName(`${deps.basePath}/session/${sessionId}${rest.split('?')[0]}`, req.method)
169
+ : undefined;
170
+ if (hooks && !(yield hooks.before(sessionId, command, req, res)))
171
+ return;
172
+ // A client that goes away takes its forwarded command with it.
173
+ const abort = new AbortController();
174
+ res.on('close', () => {
175
+ if (!res.writableFinished)
176
+ abort.abort();
177
+ });
178
+ let captured;
179
+ try {
180
+ const upstream = yield (0, forwardToNode_1.sendToNode)(yield nodeRequest(req, sessionId, location, abort.signal));
181
+ captured = yield (0, forwardToNode_1.relayAnswer)(upstream, res, !!hooks);
182
+ }
183
+ catch (error) {
184
+ if (abort.signal.aborted)
185
+ return;
186
+ logger.error(`Could not forward ${req.method} for session ${sessionId} to ${location.url}: ${(0, commandAuth_1.summarize)(error)}`);
187
+ if (!res.headersSent)
188
+ res.status(forwardToNode_1.NODE_UNREACHABLE_STATUS).json(forwardToNode_1.NODE_UNREACHABLE_BODY);
189
+ else
190
+ res.destroy();
191
+ return;
192
+ }
193
+ if (hooks && captured !== undefined) {
194
+ yield hooks
195
+ .after(sessionId, command, req, res, captured)
196
+ .catch((error) => logger.warn(`Dashboard log failed for session ${sessionId}: ${(0, commandAuth_1.summarize)(error)}`));
197
+ }
198
+ });
199
+ },
200
+ };
201
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SessionLocator = exports.LOCAL = void 0;
16
+ exports.sessionRowsFrom = sessionRowsFrom;
17
+ const SessionType_1 = __importDefault(require("../enums/SessionType"));
18
+ exports.LOCAL = { kind: 'local' };
19
+ class SessionLocator {
20
+ constructor(deps) {
21
+ var _a;
22
+ this.deps = deps;
23
+ this.remembered = new Map();
24
+ this.maxRemembered = (_a = deps.maxRemembered) !== null && _a !== void 0 ? _a : 1000;
25
+ }
26
+ locate(sessionId) {
27
+ return __awaiter(this, void 0, void 0, function* () {
28
+ const live = this.deps.getSession(sessionId);
29
+ if (live)
30
+ return this.locationOf(live);
31
+ const known = this.remembered.get(sessionId);
32
+ if (known)
33
+ return known;
34
+ let location = exports.LOCAL;
35
+ const row = yield this.deps.findOpenSession(sessionId);
36
+ if (row) {
37
+ const device = yield this.deps.findDevice(row.device_udid, row.node_id);
38
+ if (device && !this.deps.isLocalDevice(device)) {
39
+ location = {
40
+ kind: 'remote',
41
+ url: yield this.deps.webDriverUrlOf(device),
42
+ cloud: !!device.cloud,
43
+ };
44
+ }
45
+ }
46
+ this.remember(sessionId, location);
47
+ return location;
48
+ });
49
+ }
50
+ forget(sessionId) {
51
+ this.remembered.delete(sessionId);
52
+ }
53
+ locationOf(session) {
54
+ const type = session.getType();
55
+ const getUrl = session.getWebDriverUrl;
56
+ if (type === SessionType_1.default.LOCAL || typeof getUrl !== 'function')
57
+ return exports.LOCAL;
58
+ // A session recovered as remote whose phone is this server's own (a local
59
+ // session from before a restart) must not be sent back to this server.
60
+ if (this.deps.isLocalDevice(session.getDevice()))
61
+ return exports.LOCAL;
62
+ return {
63
+ kind: 'remote',
64
+ url: getUrl.call(session),
65
+ cloud: type === SessionType_1.default.CLOUD,
66
+ session,
67
+ };
68
+ }
69
+ remember(sessionId, location) {
70
+ if (this.remembered.size >= this.maxRemembered) {
71
+ const oldest = this.remembered.keys().next().value;
72
+ this.remembered.delete(oldest);
73
+ }
74
+ this.remembered.set(sessionId, location);
75
+ }
76
+ }
77
+ exports.SessionLocator = SessionLocator;
78
+ /** The database side of SessionLocatorDeps, on a Prisma client. */
79
+ function sessionRowsFrom(db) {
80
+ return {
81
+ findOpenSession: (sessionId) => __awaiter(this, void 0, void 0, function* () {
82
+ if (!sessionId)
83
+ return null;
84
+ const row = yield db.session.findFirst({
85
+ where: { id: sessionId, endTime: null },
86
+ select: { device_udid: true, node_id: true },
87
+ });
88
+ return row !== null && row !== void 0 ? row : null;
89
+ }),
90
+ findDevice: (udid, nodeId) => __awaiter(this, void 0, void 0, function* () {
91
+ var _a;
92
+ if (nodeId) {
93
+ const onNode = yield db.device.findFirst({ where: { udid, nodeId } });
94
+ if (onNode)
95
+ return onNode;
96
+ }
97
+ return (_a = (yield db.device.findFirst({ where: { udid } }))) !== null && _a !== void 0 ? _a : null;
98
+ }),
99
+ };
100
+ }