@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.
- package/README.md +2 -1
- package/lib/package.json +3 -4
- package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-XpewX5nn.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-BQDv3ZCC.js} +1 -1
- package/lib/public/assets/BugReportButton-C7OmS61B.js +10 -0
- package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-wl3l2Zlt.js} +1 -1
- package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-fPxujqqE.js} +1 -1
- package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-5bCutWrs.js} +1 -1
- package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-owsBiDT1.js} +1 -1
- package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-DFhqzPSt.js} +1 -1
- package/lib/public/assets/{Modal-CceoMY7D.js → Modal-Bw-a-E-e.js} +1 -1
- package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-WtByf5bC.js} +1 -1
- package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-FpWWFNgT.js} +1 -1
- package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-Jzn9c7o8.js} +1 -1
- package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-BR7nOhpT.js} +1 -1
- package/lib/public/assets/{Table-LV7zFyZg.js → Table-B_Va3V05.js} +1 -1
- package/lib/public/assets/{activity-D17TUloi.js → activity-DxRFWvkN.js} +1 -1
- package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-CQf-9_V6.js} +1 -1
- package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-SoE1Kd7N.js} +1 -1
- package/lib/public/assets/{apps-Dbnzd0fW.js → apps-CgGXTlgn.js} +1 -1
- package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-uQ6WIwiC.js} +1 -1
- package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-C9RyfzXS.js} +1 -1
- package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-DFHi_8yG.js} +1 -1
- package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-Bii1t4qc.js} +2 -2
- package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-Bnv8Z9hi.js} +1 -1
- package/lib/public/assets/{button-xtAhYR-f.js → button-_Y78wXiP.js} +1 -1
- package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-D4MqSOc2.js} +1 -1
- package/lib/public/assets/{check-CyuEoKkd.js → check-uGHCXe-L.js} +1 -1
- package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-Dy1OqpzB.js} +1 -1
- package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-BUsldEli.js} +1 -1
- package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-D3Qq0tKp.js} +1 -1
- package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-B4IOD1DE.js} +1 -1
- package/lib/public/assets/{clock-Cd-t7xpe.js → clock-BfYfzvSg.js} +1 -1
- package/lib/public/assets/{copy-C6zerX3I.js → copy-DZwYE5Va.js} +1 -1
- package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CRG4Utt5.js} +3 -3
- package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
- package/lib/public/assets/{download-x9WP7mcL.js → download-CCDYo_TN.js} +1 -1
- package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-DuLCmVFb.js} +1 -1
- package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-CtKNPqzb.js} +1 -1
- package/lib/public/assets/{index-BR1wTIPC.js → index-3uxl3o7K.js} +43 -43
- package/lib/public/assets/{index-BvH_ekRy.js → index-Zy1Op5C1.js} +1 -1
- package/lib/public/assets/{input-DejcCiTI.js → input-CzTWvUXZ.js} +1 -1
- package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CI-nYDFM.js} +1 -1
- package/lib/public/assets/{layers-BKqYLiHE.js → layers-YZZGFyTX.js} +1 -1
- package/lib/public/assets/{lock-LFsFhc17.js → lock-L8tzWgDo.js} +1 -1
- package/lib/public/assets/{login-x2pkzt-t.js → login-DBbX-fu8.js} +1 -1
- package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-AJwkOIsE.js} +1 -1
- package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-Di_66F2m.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-Cnab2zdg.js} +1 -1
- package/lib/public/assets/{network-Z_zBhpsD.js → network-DiSumY-1.js} +1 -1
- package/lib/public/assets/{overview-BY5uv08S.js → overview-CwE4gXl5.js} +1 -1
- package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CfR4Sm9V.js} +1 -1
- package/lib/public/assets/{plus-BTD0dnQ0.js → plus-DOS8P9CQ.js} +1 -1
- package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-BYpHwQIH.js} +1 -1
- package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BRVJU_qV.js} +1 -1
- package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Bi3JvAFZ.js} +1 -1
- package/lib/public/assets/{select-Uc9E5pye.js → select-Cqx7pEou.js} +1 -1
- package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C0DsncKj.js} +1 -1
- package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CAhpdilF.js} +1 -1
- package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CrCVeW2m.js} +1 -1
- package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-F_hwYqEt.js} +1 -1
- package/lib/public/assets/{settings-CkSWiZq8.js → settings-C-s5pypL.js} +1 -1
- package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-DY5uzv6A.js} +1 -1
- package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-Dli7gwyg.js} +1 -1
- package/lib/public/assets/{teams-PpoEQz31.js → teams-ui4IhHNC.js} +1 -1
- package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV4hSmXJ.js} +1 -1
- package/lib/public/assets/{upload-C3i7sAZl.js → upload-DkGy_J-L.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-BFtMqQOh.js} +1 -1
- package/lib/public/assets/{users-Drd7ifJx.js → users-BU0nXIVe.js} +1 -1
- package/lib/public/assets/{users-CEJ3fC78.js → users-Nb_rzRmF.js} +1 -1
- package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-CUNzD2kH.js} +1 -1
- package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-Ca3UFXZe.js} +1 -1
- package/lib/public/assets/{zap-4yN8Jdfw.js → zap-B2bH2vKX.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/InternalHttpClient.js +5 -1
- package/lib/src/XenonCapabilityManager.js +15 -1
- package/lib/src/app/appiumBasePath.js +32 -0
- package/lib/src/app/index.js +12 -0
- package/lib/src/app/insertBeforeRoutes.js +24 -4
- package/lib/src/app/registerCommandAuth.js +152 -33
- package/lib/src/app/routers/control.js +11 -104
- package/lib/src/app/routers/grid.js +28 -8
- package/lib/src/app/routers/nodePhoneControl.js +301 -0
- package/lib/src/app/ws/h264StreamWs.js +43 -12
- package/lib/src/app/ws/logcatWs.js +48 -14
- package/lib/src/app/ws/nodeSocketRelay.js +167 -0
- package/lib/src/app/ws/upgradeRouter.js +215 -0
- package/lib/src/dashboard/event-manager.js +7 -6
- package/lib/src/data-service/device-service.js +152 -49
- package/lib/src/data-service/device-store.js +92 -10
- package/lib/src/data-service/deviceClaims.js +116 -0
- package/lib/src/data-service/deviceFieldOwners.js +32 -10
- package/lib/src/data-service/prisma-store.js +102 -31
- package/lib/src/device-managers/AndroidDeviceManager.js +40 -42
- package/lib/src/device-managers/HealthMonitorService.js +17 -2
- package/lib/src/device-managers/IOSDeviceManager.js +5 -1
- package/lib/src/device-managers/NodeDevices.js +7 -2
- package/lib/src/device-managers/android/AndroidH264StreamService.js +13 -5
- package/lib/src/device-managers/android/AndroidStreamService.js +9 -6
- package/lib/src/device-managers/android/previewHold.js +2 -2
- package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
- package/lib/src/device-managers/ios/IOSStreamService.js +162 -40
- package/lib/src/device-managers/localDeviceHosts.js +86 -0
- package/lib/src/device-managers/ownDeviceRow.js +38 -0
- package/lib/src/device-utils.js +78 -24
- package/lib/src/gateway/createHandoff.js +73 -0
- package/lib/src/gateway/defaultGateway.js +80 -0
- package/lib/src/gateway/forwardToNode.js +147 -0
- package/lib/src/gateway/hubSessionToken.js +356 -0
- package/lib/src/gateway/internalCall.js +90 -0
- package/lib/src/gateway/nodeSessionStatus.js +140 -0
- package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
- package/lib/src/gateway/sessionCreate.js +136 -0
- package/lib/src/gateway/sessionGateway.js +201 -0
- package/lib/src/gateway/sessionLocator.js +100 -0
- package/lib/src/generated/client/edge.js +7 -4
- package/lib/src/generated/client/index-browser.js +4 -1
- package/lib/src/generated/client/index.d.ts +112 -1
- package/lib/src/generated/client/index.js +7 -4
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +9 -0
- package/lib/src/generated/client/wasm.js +4 -1
- package/lib/src/helpers/index.js +5 -1
- package/lib/src/logger.js +5 -0
- package/lib/src/middleware/authMiddleware.js +80 -0
- package/lib/src/middleware/commandAuth.js +31 -17
- package/lib/src/middleware/csrfMiddleware.js +3 -2
- package/lib/src/middleware/deviceAccessGuard.js +4 -1
- package/lib/src/middleware/sessionListingFilter.js +153 -0
- package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
- package/lib/src/plugin.js +27 -1
- package/lib/src/prisma.js +2 -1
- package/lib/src/services/DeviceReconciler.js +5 -2
- package/lib/src/services/OrphanSweeper.js +12 -19
- package/lib/src/services/PortAllocator.js +75 -16
- package/lib/src/services/ServerManager.js +63 -30
- package/lib/src/services/SessionLifecycleService.js +335 -76
- package/lib/src/services/ShutdownCoordinator.js +7 -1
- package/lib/src/services/SocketServer.js +11 -2
- package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
- package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
- package/lib/src/services/device-access/ticketActorAccess.js +3 -1
- package/lib/src/services/lease/LeaseOrphanSweeper.js +34 -6
- package/lib/src/services/lease/LeaseService.js +19 -5
- package/lib/src/services/session/nodeCreateCaps.js +55 -0
- package/lib/src/services/session/sessionCredentials.js +7 -59
- package/lib/src/sessions/CloudSession.js +12 -0
- package/lib/src/sessions/LocalSession.js +52 -26
- package/lib/src/sessions/RemoteSession.js +116 -9
- package/lib/src/sessions/SessionManager.js +49 -6
- package/lib/src/sessions/appiumUmbrella.js +32 -0
- package/lib/test/helpers/appium-umbrella.js +84 -0
- package/lib/test/helpers/fake-device-table.js +97 -0
- package/lib/test/helpers/loki-stores.js +41 -0
- package/lib/test/helpers/loopbackRequest.js +77 -0
- package/lib/test/helpers/own-node-id.js +29 -0
- package/lib/test/helpers/scratch-database.js +100 -0
- package/lib/test/helpers/scratch-port-leases.js +119 -0
- package/lib/test/helpers/test-container.js +35 -27
- package/lib/test/integration/androidDevices.spec.js +5 -4
- package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
- package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
- package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
- package/lib/test/setup/scratch-default-database.js +32 -0
- package/lib/test/unit/AndroidDeviceManager.spec.js +56 -1
- package/lib/test/unit/DeviceModel.spec.js +9 -9
- package/lib/test/unit/HealingCascade.test.js +5 -1
- package/lib/test/unit/OrphanSweeper.test.js +17 -13
- package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
- package/lib/test/unit/StreamTicketService.spec.js +4 -1
- package/lib/test/unit/TokenEndpoint.spec.js +17 -3
- package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
- package/lib/test/unit/android-h264-service.spec.js +24 -0
- package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
- package/lib/test/unit/apps-team-visibility.spec.js +16 -16
- package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
- package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
- package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
- package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
- package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
- package/lib/test/unit/authMiddleware.test.js +3 -0
- package/lib/test/unit/bug-report/route.spec.js +9 -9
- package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
- package/lib/test/unit/clipboard-route.spec.js +22 -8
- package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
- package/lib/test/unit/command-auth.spec.js +31 -31
- package/lib/test/unit/control-device-lookup.spec.js +9 -9
- package/lib/test/unit/control-forward-decision.spec.js +115 -0
- package/lib/test/unit/control-lookup-failure.spec.js +3 -3
- package/lib/test/unit/create-error-message.spec.js +43 -0
- package/lib/test/unit/create-handoff.spec.js +86 -0
- package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
- package/lib/test/unit/device-access-guard.spec.js +66 -28
- package/lib/test/unit/device-service.spec.js +4 -0
- package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
- package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
- package/lib/test/unit/device-team-guard.spec.js +20 -20
- package/lib/test/unit/device-utils.spec.js +8 -0
- package/lib/test/unit/extract-team-cap.spec.js +65 -0
- package/lib/test/unit/grid-admin-only.spec.js +3 -3
- package/lib/test/unit/healing-tier-gate.spec.js +5 -1
- package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
- package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
- package/lib/test/unit/helpers.spec.js +27 -0
- package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
- package/lib/test/unit/hub-create-once.spec.js +190 -0
- package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
- package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
- package/lib/test/unit/hub-node-control-forward.spec.js +608 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
- package/lib/test/unit/hub-node-race.spec.js +426 -0
- package/lib/test/unit/hub-node-same-host.spec.js +356 -0
- package/lib/test/unit/hub-register-removal.spec.js +165 -0
- package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
- package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
- package/lib/test/unit/hub-session-token.spec.js +265 -0
- package/lib/test/unit/insert-before-routes.spec.js +5 -5
- package/lib/test/unit/install-repository-app-team.spec.js +6 -2
- package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
- package/lib/test/unit/internal-call.spec.js +181 -0
- package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
- package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
- package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
- package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
- package/lib/test/unit/is-xenon-running.spec.js +69 -0
- package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
- package/lib/test/unit/lease/LeaseService.spec.js +28 -1
- package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
- package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +107 -0
- package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
- package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
- package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
- package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
- package/lib/test/unit/logcat-ws.spec.js +12 -1
- package/lib/test/unit/loopback-request.spec.js +81 -0
- package/lib/test/unit/mark-node-busy-loki.spec.js +53 -0
- package/lib/test/unit/node-create-caps.spec.js +54 -0
- package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
- package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
- package/lib/test/unit/node-phone-control.spec.js +131 -0
- package/lib/test/unit/node-socket-relay.spec.js +357 -0
- package/lib/test/unit/omni-vision.spec.js +7 -3
- package/lib/test/unit/plugin.spec.js +45 -4
- package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
- package/lib/test/unit/profile-router.test.js +11 -11
- package/lib/test/unit/queue-requester.spec.js +5 -0
- package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
- package/lib/test/unit/queue-visibility.spec.js +1 -1
- package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
- package/lib/test/unit/recording-instrumentation.spec.js +5 -0
- package/lib/test/unit/recording-source-ended.spec.js +5 -0
- package/lib/test/unit/recording-store.spec.js +9 -3
- package/lib/test/unit/recordings-library-routes.spec.js +51 -51
- package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
- package/lib/test/unit/remote-session-create.spec.js +130 -0
- package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
- package/lib/test/unit/reset-link.test.js +14 -14
- package/lib/test/unit/session-app-resolution.spec.js +14 -10
- package/lib/test/unit/session-assets.spec.js +9 -9
- package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
- package/lib/test/unit/session-create-layer.spec.js +255 -0
- package/lib/test/unit/session-credential-strip.spec.js +25 -9
- package/lib/test/unit/session-credentials.spec.js +0 -47
- package/lib/test/unit/session-listing-auth.spec.js +345 -0
- package/lib/test/unit/session-locator.spec.js +250 -0
- package/lib/test/unit/session-node-calls.spec.js +174 -0
- package/lib/test/unit/session-owner-resolver.spec.js +107 -0
- package/lib/test/unit/session-recovery-routing.spec.js +118 -0
- package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
- package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
- package/lib/test/unit/socketServer.bearer.spec.js +3 -1
- package/lib/test/unit/stream-leave-route.spec.js +19 -7
- package/lib/test/unit/stream-own-device-row.spec.js +164 -0
- package/lib/test/unit/stream-stop-ownership.spec.js +17 -5
- package/lib/test/unit/stream-stop-recording.spec.js +17 -5
- package/lib/test/unit/stream-ticket-identity.spec.js +26 -6
- package/lib/test/unit/stream-watchdog-timers.spec.js +46 -0
- package/lib/test/unit/upgrade-router.spec.js +446 -0
- package/lib/test/unit/users-router.test.js +11 -8
- package/lib/test/unit/verify-credential.spec.js +4 -1
- package/lib/test/unit/webdriver-info-route.spec.js +64 -0
- package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -4
- package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
- package/prisma/schema.prisma +9 -0
- package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
- package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
- package/lib/src/proxy/wd-command-proxy.js +0 -221
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as w,b as k,r as a,j as e,$ as C}from"./index-
|
|
1
|
+
import{c as w,b as k,r as a,j as e,$ as C}from"./index-3uxl3o7K.js";import{l as M,d as R,c as I,a as A,u as D}from"./users-BU0nXIVe.js";import{f as U}from"./time-B8FIgwPE.js";import{P}from"./page-header-CfR4Sm9V.js";import{B as y}from"./button-_Y78wXiP.js";import{I as N}from"./input-CzTWvUXZ.js";import{S as g}from"./select-Cqx7pEou.js";import{P as B}from"./plus-DOS8P9CQ.js";import{T as L}from"./trash-2-BV4hSmXJ.js";import"./utils-CDN07tui.js";/**
|
|
2
2
|
* @license lucide-react v0.555.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as P,r,X as c,j as e,J as u,A as b,C as U,K as D}from"./index-
|
|
1
|
+
import{t as P,r,X as c,j as e,J as u,A as b,C as U,K as D}from"./index-3uxl3o7K.js";/* empty css */import{F as S}from"./FieldGroup-5bCutWrs.js";import{P as O}from"./page-header-CfR4Sm9V.js";import{T as V}from"./trash-2-BV4hSmXJ.js";import{P as x}from"./plus-DOS8P9CQ.js";import{C as B}from"./circle-x-B4IOD1DE.js";import{C as H}from"./circle-check-D3Qq0tKp.js";import{Z as I}from"./zap-B2bH2vKX.js";import{A as J}from"./activity-DxRFWvkN.js";const C=[{id:"device_offline",label:"Device offline",icon:U,tone:"red"},{id:"device_new",label:"New device",icon:x,tone:"green"},{id:"session_failed",label:"Session failed",icon:B,tone:"amber"},{id:"selector_health_digest",label:"Selector health digest",icon:D,tone:"green"}],X=["udid","host","name","sessionId","failureReason","eventType","platform"],ae=()=>{const{toast:o}=P(),[g,T]=r.useState([]),[A,w]=r.useState(!0),[l,j]=r.useState(""),[d,k]=r.useState(["device_offline","session_failed"]),[h,f]=r.useState(!1),[v,N]=r.useState(!1),[n,_]=r.useState(!1),[y,m]=r.useState("");r.useEffect(()=>{p()},[]);const p=async()=>{w(!0);try{const s=await c.getWebhookConfigs();T(s||[])}catch(s){console.error("Failed to load webhook configs",s),o("Failed to load webhooks.","error")}finally{w(!1)}},z=async()=>{if(!l.trim()){o("Webhook URL is required.","error");return}if(d.length===0){o("Select at least one trigger event.","error");return}f(!0);try{await c.addWebhookConfig(l.trim(),d,"slack",y||void 0),j(""),m(""),_(!1),k(["device_offline","session_failed"]),await p(),o("Webhook saved.","success")}catch(s){console.error("Failed to add webhook",s),o(`Failed to add webhook: ${s.message||"unknown error"}`,"error")}finally{f(!1)}},E=async(s,t)=>{if(window.confirm(`Remove webhook for "${t}"?`))try{await c.deleteWebhookConfig(s),await p(),o("Webhook removed.","success")}catch(a){console.error("Failed to delete webhook",a),o(`Failed to delete: ${a.message||"unknown error"}`,"error")}},L=async()=>{if(l.trim()){N(!0);try{await c.testWebhook(l.trim(),"slack"),o("Test payload delivered.","success")}catch(s){o(`Test failed: ${s.message||"check the URL"}`,"error")}finally{N(!1)}}},$=s=>{k(t=>t.includes(s)?t.filter(a=>a!==s):[...t,s])},R=s=>{m(t=>t+`{{${s}}} `)};return e.jsxs("div",{className:"settings-container",children:[e.jsx(O,{icon:u,title:"Notifications",subtitle:"Configure Slack or generic webhooks to receive alerts for critical infrastructure events."}),e.jsxs("div",{className:"settings-content",children:[A?e.jsxs("div",{className:"settings-loading",style:{height:200},children:[e.jsx(b,{className:"animate-spin",size:28}),e.jsx("span",{children:"Loading webhooks…"})]}):g.length===0?e.jsxs("div",{className:"empty-state",children:[e.jsx("div",{className:"empty-state__icon",children:e.jsx(u,{size:28})}),e.jsx("h3",{className:"empty-state__title",children:"No webhooks configured"}),e.jsx("p",{className:"empty-state__copy",children:"Wire up a Slack incoming webhook or any HTTPS endpoint to receive alerts when devices go offline, new devices register, or sessions fail. Configure your first webhook below."})]}):e.jsx("div",{className:"webhook-list-grid",children:g.map(s=>{const t=(()=>{try{return JSON.parse(s.events)}catch{return[]}})();return e.jsxs("div",{className:"webhook-row-card",children:[e.jsxs("div",{className:"webhook-row-card__header",children:[e.jsx("span",{className:`pill-chip ${s.payloadTemplate?"pill-chip--admin":"pill-chip--scope"}`,children:s.payloadTemplate?"CUSTOM":"SLACK"}),e.jsx("span",{className:"webhook-row-card__url",title:s.url,children:s.url}),e.jsxs("button",{type:"button",className:"row-action row-action--danger",onClick:()=>E(s.id,s.url),"aria-label":`Remove webhook for ${s.url}`,children:[e.jsx(V,{size:13}),e.jsx("span",{children:"Remove"})]})]}),e.jsx("div",{className:"webhook-row-card__events",children:t.length===0?e.jsx("span",{className:"webhook-row-card__no-events",children:"No triggers"}):t.map(a=>{const i=C.find(F=>F.id===a),W=(i==null?void 0:i.icon)??u;return e.jsxs("span",{className:`event-pill event-pill--${(i==null?void 0:i.tone)||"gray"}`,children:[e.jsx(W,{size:11}),(i==null?void 0:i.label)||a]},a)})})]},s.id)})}),e.jsxs("div",{className:"add-webhook-card",children:[e.jsxs("div",{className:"add-webhook-card__header",children:[e.jsx(x,{size:14}),e.jsx("span",{children:"Add a new webhook"})]}),e.jsxs("div",{className:"add-webhook-card__body",children:[e.jsx(S,{label:"Webhook URL",description:"The endpoint we POST webhook events to.",htmlFor:"webhook-url",children:e.jsx("div",{className:"setting-input-wrapper",children:e.jsx("input",{id:"webhook-url",type:"url",placeholder:"https://hooks.slack.com/services/...",value:l,onChange:s=>j(s.target.value)})})}),e.jsx(S,{label:"Trigger events",children:e.jsx("div",{className:"event-toggle-grid",children:C.map(s=>{const t=d.includes(s.id),a=s.icon;return e.jsxs("button",{type:"button",role:"checkbox","aria-checked":t,className:`event-toggle event-toggle--${s.tone} ${t?"is-selected":""}`,onClick:()=>$(s.id),children:[e.jsx(a,{size:14,className:"event-toggle__lead"}),e.jsx("span",{children:s.label}),t&&e.jsx(H,{size:14,className:"event-toggle__check"})]},s.id)})})}),e.jsxs("div",{className:"template-section",children:[e.jsxs("button",{type:"button",className:"template-section__toggle",onClick:()=>_(!n),"aria-expanded":n,children:[e.jsx(I,{size:14,className:n?"tone-amber":"tone-dim"}),e.jsx("span",{children:"Use custom payload (optional)"}),e.jsx("span",{className:"template-section__indicator",children:n?"−":"+"})]}),n&&e.jsxs("div",{className:"template-editor",children:[e.jsx("p",{className:"template-editor__hint",children:"Define a JSON or text template. Click a variable below to insert it."}),e.jsx("div",{className:"template-editor__chips",children:X.map(s=>e.jsx("button",{type:"button",className:"template-editor__chip",onClick:()=>R(s),children:`{{${s}}}`},s))}),e.jsx("textarea",{className:"template-editor__textarea",placeholder:'Example: { "text": "Alert: Device {{udid}} is offline!" }',value:y,onChange:s=>m(s.target.value),rows:4})]})]})]}),e.jsxs("div",{className:"add-webhook-card__footer",children:[e.jsxs("button",{type:"button",className:"page-header-action page-header-action--ghost",onClick:L,disabled:!l.trim()||v,children:[v?e.jsx(b,{size:14,className:"animate-spin"}):e.jsx(J,{size:14}),e.jsx("span",{children:"Test payload"})]}),e.jsxs("button",{type:"button",className:"page-header-action",onClick:z,disabled:h||!l.trim(),children:[h?e.jsx(b,{size:14,className:"animate-spin"}):e.jsx(x,{size:14}),e.jsx("span",{children:h?"Saving…":"Save webhook"})]})]})]})]})]})};export{ae as WebhookSettings};
|
package/lib/public/index.html
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
Learn how to configure a non-root public URL by running `npm run build`.
|
|
37
37
|
-->
|
|
38
38
|
<title>Xenon</title>
|
|
39
|
-
<script type="module" crossorigin src="/xenon/assets/index-
|
|
39
|
+
<script type="module" crossorigin src="/xenon/assets/index-3uxl3o7K.js"></script>
|
|
40
40
|
<link rel="stylesheet" crossorigin href="/xenon/assets/index-hymrhlEZ.css">
|
|
41
41
|
</head>
|
|
42
42
|
|
|
@@ -55,7 +55,8 @@ const logger_1 = __importDefault(require("./logger"));
|
|
|
55
55
|
*
|
|
56
56
|
* Features:
|
|
57
57
|
* - Keep-alive connections for performance
|
|
58
|
-
* - Automatic retry with exponential backoff
|
|
58
|
+
* - Automatic retry with exponential backoff, except for a request that sets
|
|
59
|
+
* `retry: false` (InternalRequestConfig)
|
|
59
60
|
* - Request/response logging for debugging
|
|
60
61
|
* - Correlation ID tracking
|
|
61
62
|
*/
|
|
@@ -178,6 +179,9 @@ class InternalHttpClient {
|
|
|
178
179
|
logger_1.default.warn(`[HTTP ←] ${(_c = config === null || config === void 0 ? void 0 : config.method) === null || _c === void 0 ? void 0 : _c.toUpperCase()} ${config === null || config === void 0 ? void 0 : config.url} ` +
|
|
179
180
|
`[${status || 'ERR'}] ${duration}ms - ${error.message}`);
|
|
180
181
|
}
|
|
182
|
+
if (config.retry === false) {
|
|
183
|
+
return Promise.reject(error);
|
|
184
|
+
}
|
|
181
185
|
const decision = InternalHttpClient.classifyRetry(error);
|
|
182
186
|
if (!decision.retryable || config.retryCount >= InternalHttpClient.MAX_RETRIES) {
|
|
183
187
|
return Promise.reject(error);
|
|
@@ -206,9 +206,23 @@ function iOSCapabilities(caps, freeDevice) {
|
|
|
206
206
|
deleteMatch.forEach((value) => deleteAlwaysMatch(caps, value));
|
|
207
207
|
});
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* The team a session asks to be placed in, or undefined. Documented as
|
|
211
|
+
* `xe:options.team` (`teamId` is read too), with `xenon:options` as the
|
|
212
|
+
* alias, through xenonOptionsOf like every other Xenon option. The flat caps
|
|
213
|
+
* older clients send (`xenon:team`, `xe:teamId`, ...) still work; an options
|
|
214
|
+
* field wins over them. `df:` is never read.
|
|
215
|
+
*
|
|
216
|
+
* The merge is field by field, so if a request spells it `team` in one
|
|
217
|
+
* namespace and `teamId` in the other, `team` wins.
|
|
218
|
+
*/
|
|
209
219
|
function extractTeamCap(caps) {
|
|
210
220
|
var _a;
|
|
211
|
-
const
|
|
221
|
+
const options = (0, xenonOptions_1.xenonOptionsOf)(caps);
|
|
222
|
+
const fromOptions = (_a = (0, xenonOptions_1.stringOption)(options, 'team')) !== null && _a !== void 0 ? _a : (0, xenonOptions_1.stringOption)(options, 'teamId');
|
|
223
|
+
if (fromOptions)
|
|
224
|
+
return fromOptions;
|
|
225
|
+
const merged = (0, xenonOptions_1.mergedFirstMatch)(caps);
|
|
212
226
|
const prefixes = ['xenon:', 'xe:', 'appium:', ''];
|
|
213
227
|
const names = ['teamId', 'team_id', 'team'];
|
|
214
228
|
for (const prefix of prefixes) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeBasePath = normalizeBasePath;
|
|
4
|
+
exports.appiumPathname = appiumPathname;
|
|
5
|
+
/**
|
|
6
|
+
* Appium's base-path rule (base-driver `normalizeBasePath`): drop one trailing
|
|
7
|
+
* '/', and add a leading '/' unless the path is empty. `cliArgs.basePath`
|
|
8
|
+
* reaches a plugin as the operator wrote it, while Appium normalises its own
|
|
9
|
+
* copy before building routes and WebSocket paths, so anything placed in front
|
|
10
|
+
* of those must normalise the same way or it would miss what it guards.
|
|
11
|
+
*/
|
|
12
|
+
function normalizeBasePath(basePath) {
|
|
13
|
+
let normalized = typeof basePath === 'string' ? basePath : '';
|
|
14
|
+
normalized = normalized.replace(/\/$/, '');
|
|
15
|
+
if (normalized !== '' && !normalized.startsWith('/'))
|
|
16
|
+
normalized = `/${normalized}`;
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The pathname Appium matches an upgrade against, computed as
|
|
21
|
+
* base-driver's tryHandleWebSocketUpgrade does: WHATWG URL parsing, which
|
|
22
|
+
* resolves dot segments (also `%2e%2e`) and drops the query and an absolute
|
|
23
|
+
* form's host.
|
|
24
|
+
*/
|
|
25
|
+
function appiumPathname(url) {
|
|
26
|
+
try {
|
|
27
|
+
return new URL(url, 'http://localhost').pathname;
|
|
28
|
+
}
|
|
29
|
+
catch (_a) {
|
|
30
|
+
return url;
|
|
31
|
+
}
|
|
32
|
+
}
|
package/lib/src/app/index.js
CHANGED
|
@@ -86,6 +86,9 @@ const rateLimitMiddleware_1 = require("../middleware/rateLimitMiddleware");
|
|
|
86
86
|
const csrfMiddleware_1 = require("../middleware/csrfMiddleware");
|
|
87
87
|
const swagger_1 = require("./swagger");
|
|
88
88
|
const typedi_1 = require("typedi");
|
|
89
|
+
const PluginContext_1 = require("../PluginContext");
|
|
90
|
+
const nodeWebDriverUrl_1 = require("../gateway/nodeWebDriverUrl");
|
|
91
|
+
const nodeSessionStatus_1 = require("../gateway/nodeSessionStatus");
|
|
89
92
|
const dashboardPluginUrl = null;
|
|
90
93
|
const ASYNC_LOCK = new async_lock_1.default();
|
|
91
94
|
const router = express_1.default.Router(), apiRouter = express_1.default.Router(), staticFilesRouter = express_1.default.Router();
|
|
@@ -228,6 +231,15 @@ function createRouter(pluginArgs) {
|
|
|
228
231
|
apiRouter.use(csrfMiddleware_1.csrfMiddleware);
|
|
229
232
|
// Health endpoint: no auth, no rate limit
|
|
230
233
|
apiRouter.get('/health', (_req, res) => res.json({ ok: true }));
|
|
234
|
+
// Where this server's WebDriver API lives: a hub asks a node before sending
|
|
235
|
+
// it a session or a command (gateway/nodeWebDriverUrl.ts). No login: the
|
|
236
|
+
// hub holds no credentials for the node, and the base path is in every
|
|
237
|
+
// client's URL anyway.
|
|
238
|
+
apiRouter.get('/webdriver', (0, nodeWebDriverUrl_1.webdriverInfoHandler)(() => typedi_1.Container.get(PluginContext_1.PluginContext).nodeBasePath));
|
|
239
|
+
// On a node: whether a session exists, for the hub's heartbeat, answered
|
|
240
|
+
// without sending the session a command. No login (the hub holds no
|
|
241
|
+
// credentials for the node); it takes the hub's session token instead.
|
|
242
|
+
(0, nodeSessionStatus_1.registerNodeSessionStatus)(apiRouter, pluginArgs);
|
|
231
243
|
// Dashboard login: unauthenticated (rate-limited internally via separate IP logic)
|
|
232
244
|
apiRouter.use('/auth', (0, auth_1.authPublicRouter)()); // login, logout — unauthenticated
|
|
233
245
|
// All remaining /api/* requires authentication (cookie session for
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.routerStackOf = routerStackOf;
|
|
4
4
|
exports.insertBeforeRoutes = insertBeforeRoutes;
|
|
5
|
+
exports.insertAtStart = insertAtStart;
|
|
5
6
|
function isRouter(candidate) {
|
|
6
7
|
return !!candidate && Array.isArray(candidate.stack);
|
|
7
8
|
}
|
|
@@ -28,7 +29,11 @@ function routerStackOf(app) {
|
|
|
28
29
|
var _a;
|
|
29
30
|
return (_a = findRouter(app)) === null || _a === void 0 ? void 0 : _a.router.stack;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Let `app.use()` build one layer, then move it to `positionOf(stack)` (the
|
|
34
|
+
* stack without the new layer).
|
|
35
|
+
*/
|
|
36
|
+
function spliceNewLayer(app, use, positionOf) {
|
|
32
37
|
const found = findRouter(app);
|
|
33
38
|
if (!found) {
|
|
34
39
|
return {
|
|
@@ -38,7 +43,7 @@ function insertBeforeRoutes(app, path, handler) {
|
|
|
38
43
|
}
|
|
39
44
|
const { stack } = found.router;
|
|
40
45
|
const lengthBefore = stack.length;
|
|
41
|
-
|
|
46
|
+
use();
|
|
42
47
|
const added = stack.splice(lengthBefore);
|
|
43
48
|
if (added.length !== 1) {
|
|
44
49
|
// app.use() added its layer somewhere other than the stack we found, or
|
|
@@ -50,8 +55,23 @@ function insertBeforeRoutes(app, path, handler) {
|
|
|
50
55
|
reason: `app.use() added ${added.length} layers to the router stack, expected 1`,
|
|
51
56
|
};
|
|
52
57
|
}
|
|
53
|
-
const
|
|
54
|
-
const index = firstRoute === -1 ? stack.length : firstRoute;
|
|
58
|
+
const index = positionOf(stack);
|
|
55
59
|
stack.splice(index, 0, added[0]);
|
|
56
60
|
return { placed: true, shape: found.shape, index };
|
|
57
61
|
}
|
|
62
|
+
function insertBeforeRoutes(app, path, handler) {
|
|
63
|
+
return spliceNewLayer(app, () => app.use(path, handler), (stack) => {
|
|
64
|
+
const firstRoute = stack.findIndex((layer) => !!layer.route);
|
|
65
|
+
return firstRoute === -1 ? stack.length : firstRoute;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Put a path-less middleware at the very front of the router stack, ahead of
|
|
70
|
+
* middleware as well as routes. For a check that must run before one of
|
|
71
|
+
* Appium's own middlewares (its WebSocket `handleUpgrade`), which sit ahead of
|
|
72
|
+
* the routes. On Express 4 that is also ahead of `query` and `expressInit`, so
|
|
73
|
+
* the handler must use only Node's own request and response API.
|
|
74
|
+
*/
|
|
75
|
+
function insertAtStart(app, handler) {
|
|
76
|
+
return spliceNewLayer(app, () => app.use(handler), () => 0);
|
|
77
|
+
}
|
|
@@ -1,67 +1,186 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
2
13
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
14
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
15
|
};
|
|
5
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.normalizeBasePath =
|
|
17
|
+
exports.normalizeBasePath = void 0;
|
|
7
18
|
exports.sessionCommandPath = sessionCommandPath;
|
|
19
|
+
exports.sessionListingPath = sessionListingPath;
|
|
20
|
+
exports.commandAuthDeps = commandAuthDeps;
|
|
21
|
+
exports.sessionCreatePath = sessionCreatePath;
|
|
22
|
+
exports.registerSessionGateway = registerSessionGateway;
|
|
8
23
|
exports.registerCommandAuth = registerCommandAuth;
|
|
24
|
+
exports.registerSessionUpgradeGuard = registerSessionUpgradeGuard;
|
|
9
25
|
const typedi_1 = require("typedi");
|
|
10
26
|
const logger_1 = __importDefault(require("../logger"));
|
|
11
27
|
const config_1 = require("../config");
|
|
12
28
|
const SessionOwnerResolver_1 = require("../services/device-access/SessionOwnerResolver");
|
|
13
29
|
const commandAuth_1 = require("../middleware/commandAuth");
|
|
14
30
|
const commandCaller_1 = require("../middleware/commandCaller");
|
|
31
|
+
const sessionListingFilter_1 = require("../middleware/sessionListingFilter");
|
|
32
|
+
const sessionUpgradeGuard_1 = require("../middleware/sessionUpgradeGuard");
|
|
33
|
+
const internalCall_1 = require("../gateway/internalCall");
|
|
34
|
+
const sessionGateway_1 = require("../gateway/sessionGateway");
|
|
35
|
+
const sessionCreate_1 = require("../gateway/sessionCreate");
|
|
36
|
+
const appiumBasePath_1 = require("./appiumBasePath");
|
|
37
|
+
Object.defineProperty(exports, "normalizeBasePath", { enumerable: true, get: function () { return appiumBasePath_1.normalizeBasePath; } });
|
|
15
38
|
const insertBeforeRoutes_1 = require("./insertBeforeRoutes");
|
|
16
|
-
/**
|
|
17
|
-
* Appium's base-path rule (base-driver `normalizeBasePath`): drop one trailing
|
|
18
|
-
* '/', and add a leading '/' unless the path is empty. `cliArgs.basePath`
|
|
19
|
-
* reaches a plugin as the operator wrote it, while Appium normalises its own
|
|
20
|
-
* copy before building routes, so the guard must normalise the same way or it
|
|
21
|
-
* would miss the routes it is meant to sit in front of.
|
|
22
|
-
*/
|
|
23
|
-
function normalizeBasePath(basePath) {
|
|
24
|
-
let normalized = typeof basePath === 'string' ? basePath : '';
|
|
25
|
-
normalized = normalized.replace(/\/$/, '');
|
|
26
|
-
if (normalized !== '' && !normalized.startsWith('/'))
|
|
27
|
-
normalized = `/${normalized}`;
|
|
28
|
-
return normalized;
|
|
29
|
-
}
|
|
30
39
|
/**
|
|
31
40
|
* Everything under `<basePath>/session/:sessionId`, every method. The create
|
|
32
41
|
* route (`POST <basePath>/session`) has no id segment and does not match.
|
|
33
42
|
*/
|
|
34
43
|
function sessionCommandPath(basePath) {
|
|
35
|
-
return `${normalizeBasePath(basePath)}/session/:sessionId`;
|
|
44
|
+
return `${(0, appiumBasePath_1.normalizeBasePath)(basePath)}/session/:sessionId`;
|
|
45
|
+
}
|
|
46
|
+
/** Appium 3's session listing route (base-driver `getAppiumSessions`). */
|
|
47
|
+
function sessionListingPath(basePath) {
|
|
48
|
+
return `${(0, appiumBasePath_1.normalizeBasePath)(basePath)}/appium/sessions`;
|
|
36
49
|
}
|
|
37
50
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
51
|
+
* The dependencies per-command auth runs with. ServerManager builds one set
|
|
52
|
+
* and hands it to both registrations below, so commands, the session listing
|
|
53
|
+
* and session WebSockets share one credential cache.
|
|
54
|
+
*/
|
|
55
|
+
function commandAuthDeps(overrides = {}) {
|
|
56
|
+
var _a, _b, _c, _d, _e, _f;
|
|
57
|
+
return {
|
|
58
|
+
enabled: (_a = overrides.enabled) !== null && _a !== void 0 ? _a : (() => (0, commandAuth_1.commandAuthEnabled)()),
|
|
59
|
+
authDisabled: (_b = overrides.authDisabled) !== null && _b !== void 0 ? _b : (() => config_1.config.authDisabled === true),
|
|
60
|
+
verifier: (_c = overrides.verifier) !== null && _c !== void 0 ? _c : new commandCaller_1.CommandCallerVerifier(),
|
|
61
|
+
ownerOf: (_d = overrides.ownerOf) !== null && _d !== void 0 ? _d : ((sessionId) => typedi_1.Container.get(SessionOwnerResolver_1.SessionOwnerResolver).ownerOf(sessionId)),
|
|
62
|
+
ownersOf: (_e = overrides.ownersOf) !== null && _e !== void 0 ? _e : ((sessionIds) => typedi_1.Container.get(SessionOwnerResolver_1.SessionOwnerResolver).ownersOf(sessionIds)),
|
|
63
|
+
logger: (_f = overrides.logger) !== null && _f !== void 0 ? _f : logger_1.default.scope('CommandAuth'),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const refuseToStart = (what) => new Error(`XENON_REQUIRE_COMMAND_AUTH is on, but ${what}. Refusing to start rather than ` +
|
|
67
|
+
'serve sessions unchecked.');
|
|
68
|
+
/** The create route (`POST <basePath>/session`), where the create layer is mounted. */
|
|
69
|
+
function sessionCreatePath(basePath) {
|
|
70
|
+
return `${(0, appiumBasePath_1.normalizeBasePath)(basePath)}/session`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Install the session gateway (gateway/sessionGateway.ts) and the
|
|
74
|
+
* session-listing filter (sessionListingFilter.ts) ahead of Appium's routes.
|
|
75
|
+
* Called from ServerManager.registerRoutes, which Appium runs after adding
|
|
76
|
+
* them. The gateway is three adjacent layers:
|
|
77
|
+
*
|
|
78
|
+
* 1. the internal-call layer (gateway/internalCall.ts), path-less, which turns
|
|
79
|
+
* Xenon's own `<basePath>/wd-internal/...` calls, when they carry this
|
|
80
|
+
* process's secret, into the plain session command;
|
|
81
|
+
* 2. with `options.create`, the create layer at `<basePath>/session`
|
|
82
|
+
* (gateway/sessionCreate.ts), which allocates the phone for
|
|
83
|
+
* `POST <basePath>/session` and creates a remote phone's session itself;
|
|
84
|
+
* 3. the session layer at `<basePath>/session/:sessionId`: per-command auth,
|
|
85
|
+
* then, on a hub, forwarding of the sessions other servers run.
|
|
86
|
+
*
|
|
87
|
+
* The first comes first, so the others see an internal call by its real path
|
|
88
|
+
* and know it for one. It is path-less because Express puts a mount path back
|
|
89
|
+
* onto the URL when a mounted layer calls next(), which would undo the strip.
|
|
90
|
+
* The others are mounted, so their path matching is the router's own.
|
|
91
|
+
*
|
|
92
|
+
* All of it is always installed and reads the setting per request, so a
|
|
93
|
+
* server with per-command auth off pays one env check per session command and
|
|
94
|
+
* does no lookups for it. If a layer cannot be placed ahead of the routes the
|
|
95
|
+
* error is logged, and with the setting on (and auth enabled) the server
|
|
96
|
+
* refuses to start: an operator who asked for the check must not get a server
|
|
97
|
+
* that silently serves session commands without it.
|
|
40
98
|
*
|
|
41
|
-
*
|
|
42
|
-
* server with the setting off pays one env check per session command and does
|
|
43
|
-
* no lookups. If it cannot be placed ahead of the routes the error is logged,
|
|
44
|
-
* and with the setting on (and auth enabled) the server refuses to start:
|
|
45
|
-
* an operator who asked for the check must not get a server that silently
|
|
46
|
-
* serves session commands without it.
|
|
99
|
+
* Returns where the session layer was placed.
|
|
47
100
|
*/
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
const path = sessionCommandPath(cliArgs === null || cliArgs === void 0 ? void 0 : cliArgs.basePath);
|
|
51
|
-
const result = (0, insertBeforeRoutes_1.insertBeforeRoutes)(app, path, (0, commandAuth_1.createCommandAuthMiddleware)(deps));
|
|
101
|
+
function registerSessionGateway(app, cliArgs, deps, options = {}) {
|
|
102
|
+
var _a;
|
|
52
103
|
const active = deps.enabled() && !deps.authDisabled();
|
|
104
|
+
const basePath = (0, appiumBasePath_1.normalizeBasePath)(cliArgs === null || cliArgs === void 0 ? void 0 : cliArgs.basePath);
|
|
105
|
+
const { create } = options, gateway = __rest(options, ["create"]);
|
|
106
|
+
const path = sessionCommandPath(basePath);
|
|
107
|
+
const internal = (0, insertBeforeRoutes_1.insertBeforeRoutes)(app, '/', (0, internalCall_1.createInternalCallLayer)(basePath));
|
|
108
|
+
const created = internal.placed && create
|
|
109
|
+
? (0, insertBeforeRoutes_1.insertBeforeRoutes)(app, sessionCreatePath(basePath), (0, sessionCreate_1.createSessionCreateLayer)(Object.assign(Object.assign({}, create), { authDisabled: deps.authDisabled, logger: (_a = create.logger) !== null && _a !== void 0 ? _a : deps.logger })))
|
|
110
|
+
: internal;
|
|
111
|
+
const result = created.placed
|
|
112
|
+
? (0, insertBeforeRoutes_1.insertBeforeRoutes)(app, path, (0, sessionGateway_1.createSessionGatewayLayer)(Object.assign(Object.assign({}, gateway), { auth: deps })))
|
|
113
|
+
: created;
|
|
53
114
|
if (!result.placed) {
|
|
54
115
|
deps.logger.error(`Per-command auth could not be placed ahead of Appium's routes for ${path}: ${result.reason}.`);
|
|
55
116
|
if (active) {
|
|
56
|
-
throw
|
|
57
|
-
|
|
58
|
-
|
|
117
|
+
throw refuseToStart("the per-command check could not be placed ahead of Appium's WebDriver routes, " +
|
|
118
|
+
'so it would never run');
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
const listingPath = sessionListingPath(cliArgs === null || cliArgs === void 0 ? void 0 : cliArgs.basePath);
|
|
123
|
+
const listing = (0, insertBeforeRoutes_1.insertBeforeRoutes)(app, listingPath, (0, sessionListingFilter_1.createSessionListingFilter)(deps));
|
|
124
|
+
if (!listing.placed) {
|
|
125
|
+
deps.logger.error("The session-listing filter could not be placed ahead of Appium's routes for " +
|
|
126
|
+
`${listingPath}: ${listing.reason}.`);
|
|
127
|
+
if (active) {
|
|
128
|
+
throw refuseToStart(`the session-listing filter could not be placed ahead of ${listingPath}`);
|
|
59
129
|
}
|
|
60
130
|
return result;
|
|
61
131
|
}
|
|
62
132
|
deps.logger.info(active
|
|
63
133
|
? `Per-command auth is ON: every request under ${path} needs the session owner's ` +
|
|
64
|
-
|
|
65
|
-
|
|
134
|
+
`credentials or an override admin, and ${listingPath} lists only the caller's ` +
|
|
135
|
+
'sessions (XENON_REQUIRE_COMMAND_AUTH).'
|
|
136
|
+
: `Per-command auth is off for ${path} and ${listingPath} ` +
|
|
137
|
+
'(XENON_REQUIRE_COMMAND_AUTH not set, or auth disabled).');
|
|
66
138
|
return result;
|
|
67
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Per-command auth (commandAuth.ts) and the session-listing filter, through the
|
|
142
|
+
* session gateway with no routing: a server that runs every session itself.
|
|
143
|
+
*/
|
|
144
|
+
function registerCommandAuth(app, cliArgs, overrides = {}) {
|
|
145
|
+
return registerSessionGateway(app, cliArgs, commandAuthDeps(overrides));
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Install the session WebSocket guard (sessionUpgradeGuard.ts) on both of the
|
|
149
|
+
* paths Appium can take an upgrade by: the http.Server's `upgrade` event and
|
|
150
|
+
* the front of the Express stack. Called from ServerManager.updateServer with
|
|
151
|
+
* the http.Server Appium hands plugins.
|
|
152
|
+
*
|
|
153
|
+
* Like registerCommandAuth, it is always installed and reads the setting per
|
|
154
|
+
* upgrade; with the setting off every upgrade passes straight through. If
|
|
155
|
+
* either hook cannot be installed the error is logged, and with the setting on
|
|
156
|
+
* the server refuses to start.
|
|
157
|
+
*/
|
|
158
|
+
function registerSessionUpgradeGuard(httpServer, app, cliArgs, overrides = {}) {
|
|
159
|
+
const deps = commandAuthDeps(overrides);
|
|
160
|
+
const active = deps.enabled() && !deps.authDisabled();
|
|
161
|
+
const basePath = (0, appiumBasePath_1.normalizeBasePath)(cliArgs === null || cliArgs === void 0 ? void 0 : cliArgs.basePath);
|
|
162
|
+
const guard = (0, sessionUpgradeGuard_1.createSessionUpgradeGuard)(basePath, deps);
|
|
163
|
+
const problems = [];
|
|
164
|
+
if (httpServer && typeof httpServer.emit === 'function') {
|
|
165
|
+
(0, sessionUpgradeGuard_1.guardUpgradeEvents)(httpServer, guard);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
problems.push('no HTTP server was given to guard');
|
|
169
|
+
}
|
|
170
|
+
const front = (0, insertBeforeRoutes_1.insertAtStart)(app, guard.middleware);
|
|
171
|
+
if (!front.placed)
|
|
172
|
+
problems.push(`the Express guard was not placed: ${front.reason}`);
|
|
173
|
+
if (problems.length > 0) {
|
|
174
|
+
const reason = problems.join('; ');
|
|
175
|
+
deps.logger.error(`The session WebSocket guard could not be installed: ${reason}.`);
|
|
176
|
+
if (active) {
|
|
177
|
+
throw refuseToStart(`session WebSockets (${basePath}/bidi/<id>, /ws/session/<id>/...) could not be guarded`);
|
|
178
|
+
}
|
|
179
|
+
return { placed: false, shape: front.shape, reason };
|
|
180
|
+
}
|
|
181
|
+
deps.logger.info(active
|
|
182
|
+
? `Session WebSocket guard is ON: upgrades to ${basePath}/bidi/:sessionId and ` +
|
|
183
|
+
"/ws/session/:sessionId/... need the session owner's credentials or an override admin."
|
|
184
|
+
: 'Session WebSocket guard is off (XENON_REQUIRE_COMMAND_AUTH not set, or auth disabled).');
|
|
185
|
+
return front;
|
|
186
|
+
}
|