@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
package/lib/src/plugin.js
CHANGED
|
@@ -60,9 +60,12 @@ const AICommandService_1 = require("./services/AICommandService");
|
|
|
60
60
|
const TracingService_1 = require("./services/TracingService");
|
|
61
61
|
const CommandInterceptor_1 = require("./interceptors/CommandInterceptor");
|
|
62
62
|
const SessionLifecycleService_1 = require("./services/SessionLifecycleService");
|
|
63
|
+
const createHandoff_1 = require("./gateway/createHandoff");
|
|
63
64
|
const ServerManager_1 = require("./services/ServerManager");
|
|
64
65
|
const IPluginArgs_1 = require("./interfaces/IPluginArgs");
|
|
65
66
|
const NodeDevices_1 = __importDefault(require("./device-managers/NodeDevices"));
|
|
67
|
+
const appiumUmbrella_1 = require("./sessions/appiumUmbrella");
|
|
68
|
+
const LiveSessionOwners_1 = require("./services/device-access/LiveSessionOwners");
|
|
66
69
|
const config_1 = require("./config");
|
|
67
70
|
const SessionManager_1 = require("./sessions/SessionManager");
|
|
68
71
|
const event_manager_1 = require("./dashboard/event-manager");
|
|
@@ -94,6 +97,7 @@ class XenonPlugin extends base_plugin_1.default {
|
|
|
94
97
|
return __awaiter(this, void 0, void 0, function* () {
|
|
95
98
|
var _a;
|
|
96
99
|
const sessionId = driver.sessionId;
|
|
100
|
+
typedi_1.Container.get(LiveSessionOwners_1.LiveSessionOwners).forget(sessionId);
|
|
97
101
|
const deviceFilter = {
|
|
98
102
|
session_id: sessionId ? sessionId : undefined,
|
|
99
103
|
udid: driver.caps && driver.caps.udid ? driver.caps.udid : undefined,
|
|
@@ -105,6 +109,10 @@ class XenonPlugin extends base_plugin_1.default {
|
|
|
105
109
|
hubToken: config_1.config.hubToken,
|
|
106
110
|
}).unblockDevice(deviceFilter);
|
|
107
111
|
}
|
|
112
|
+
else if (sessionId) {
|
|
113
|
+
// Keyed on the session: its phone may be another session's by now.
|
|
114
|
+
yield (0, device_service_1.releaseSessionDevices)(sessionId);
|
|
115
|
+
}
|
|
108
116
|
else {
|
|
109
117
|
yield (0, device_service_1.unblockDeviceMatchingFilter)(deviceFilter);
|
|
110
118
|
}
|
|
@@ -168,7 +176,25 @@ class XenonPlugin extends base_plugin_1.default {
|
|
|
168
176
|
if (caps && (!Array.isArray(caps.firstMatch) || caps.firstMatch.length === 0)) {
|
|
169
177
|
caps.firstMatch = [{}];
|
|
170
178
|
}
|
|
171
|
-
|
|
179
|
+
// The umbrella: where a node answers the hub's "does this session exist"
|
|
180
|
+
// without sending the session a command (gateway/nodeSessionStatus.ts).
|
|
181
|
+
typedi_1.Container.get(appiumUmbrella_1.AppiumUmbrella).note(driver);
|
|
182
|
+
const lifecycle = typedi_1.Container.get(SessionLifecycleService_1.SessionLifecycleService);
|
|
183
|
+
// The session gateway allocated this request's phone in front of Appium's
|
|
184
|
+
// route (gateway/sessionCreate.ts) and handed it here through the
|
|
185
|
+
// request's async context. It only ever hands over a phone this server
|
|
186
|
+
// drives: it creates a node's session itself and answers it, so Appium
|
|
187
|
+
// never has to hold a session it has no driver for.
|
|
188
|
+
const handoff = (0, createHandoff_1.currentCreateHandoff)();
|
|
189
|
+
if (handoff)
|
|
190
|
+
return yield lifecycle.completeLocalSession(handoff.take(), next, driver, caps);
|
|
191
|
+
// A create that did not come through the gateway (it could not be placed
|
|
192
|
+
// ahead of Appium's routes). A phone this server drives is still created
|
|
193
|
+
// here; one on another server is refused, since answering for Appium
|
|
194
|
+
// without calling next() is what broke its umbrella driver.
|
|
195
|
+
this.xenonLog.warn("createSession reached the plugin without the session gateway's allocation; allocating " +
|
|
196
|
+
'here. Sessions on phones other servers drive need the session gateway, and are refused.');
|
|
197
|
+
return yield lifecycle.createSession(next, driver, caps, { localOnly: true });
|
|
172
198
|
});
|
|
173
199
|
}
|
|
174
200
|
deleteSession(next, driver, sessionId) {
|
package/lib/src/prisma.js
CHANGED
|
@@ -36,7 +36,8 @@ const MODEL_DELEGATES = new Set([
|
|
|
36
36
|
'build', 'session', 'sessionLog', 'log', 'profiling', 'app', 'device',
|
|
37
37
|
'pendingSession', 'cLIArgs', 'webhookConfig', 'webConfig', 'locatorEtalon',
|
|
38
38
|
'lease', 'portLease', 'apiKey', 'selectorState', 'user', 'userSession',
|
|
39
|
-
'passwordResetToken', 'team', 'teamMember', 'eventLog', 'project',
|
|
39
|
+
'passwordResetToken', 'team', 'teamMember', 'eventLog', 'project', 'recording', 'bookmark',
|
|
40
|
+
'annotation',
|
|
40
41
|
]);
|
|
41
42
|
/** Cache of plain-object wrappers, keyed by model name. */
|
|
42
43
|
const _modelWrappers = new Map();
|
|
@@ -78,8 +78,11 @@ let DeviceReconciler = DeviceReconciler_1 = class DeviceReconciler {
|
|
|
78
78
|
.join(', ')}`);
|
|
79
79
|
for (const device of orphans) {
|
|
80
80
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// Keyed on the session read above: if it ended meanwhile and the
|
|
82
|
+
// phone went to another session, that one keeps it.
|
|
83
|
+
if (yield (0, device_service_1.releaseSessionDevice)(device.udid, device.host, device.session_id)) {
|
|
84
|
+
this.orphansFreed++;
|
|
85
|
+
}
|
|
83
86
|
}
|
|
84
87
|
catch (err) {
|
|
85
88
|
this.logger.error(`Failed to unblock ghost device ${device.udid}: ${err.message}`);
|
|
@@ -24,6 +24,7 @@ const prisma_1 = require("../prisma");
|
|
|
24
24
|
const logger_1 = __importDefault(require("../logger"));
|
|
25
25
|
const event_manager_1 = require("../dashboard/event-manager");
|
|
26
26
|
const SessionStatus_1 = require("../types/SessionStatus");
|
|
27
|
+
const device_service_1 = require("../data-service/device-service");
|
|
27
28
|
let OrphanSweeper = class OrphanSweeper {
|
|
28
29
|
constructor() {
|
|
29
30
|
this.log = logger_1.default.scope('OrphanSweeper');
|
|
@@ -60,25 +61,17 @@ let OrphanSweeper = class OrphanSweeper {
|
|
|
60
61
|
this.log.info(`Sweeping ${stale.length} orphaned session(s)`);
|
|
61
62
|
for (const s of stale) {
|
|
62
63
|
try {
|
|
63
|
-
yield prisma_1.prisma
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
data: {
|
|
75
|
-
busy: false,
|
|
76
|
-
session_id: null,
|
|
77
|
-
owningSessionId: null,
|
|
78
|
-
lockedAt: null,
|
|
79
|
-
},
|
|
80
|
-
}),
|
|
81
|
-
]);
|
|
64
|
+
yield prisma_1.prisma.session.update({
|
|
65
|
+
where: { id: s.id },
|
|
66
|
+
data: {
|
|
67
|
+
status: 'failed',
|
|
68
|
+
failure_reason: 'Session heartbeat timeout',
|
|
69
|
+
endTime: new Date(),
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
// This session's claim only, not every row with its udid: the phone
|
|
73
|
+
// may be another session's by now (deviceClaims.ts).
|
|
74
|
+
yield (0, device_service_1.releaseSessionDevices)(s.id);
|
|
82
75
|
// Event emit stays outside the transaction (side-effect with own error boundary)
|
|
83
76
|
yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStopped(s.id, SessionStatus_1.SessionStatus.FAILED, 'Session heartbeat timeout');
|
|
84
77
|
}
|
|
@@ -32,6 +32,13 @@ const DEFAULT_RANGES = {
|
|
|
32
32
|
system: [10100, 10199],
|
|
33
33
|
proxy: [11100, 11199],
|
|
34
34
|
};
|
|
35
|
+
/** Loopback and wildcard, in both families. See isOsFree. */
|
|
36
|
+
const PROBE_HOSTS = ['127.0.0.1', '::1', '0.0.0.0', '::'];
|
|
37
|
+
/**
|
|
38
|
+
* Bind errors meaning the host can't use that address at all (IPv6 disabled),
|
|
39
|
+
* not that something holds the port. Any other error still counts as in use.
|
|
40
|
+
*/
|
|
41
|
+
const ADDRESS_UNAVAILABLE = new Set(['EADDRNOTAVAIL', 'EAFNOSUPPORT']);
|
|
35
42
|
class PortRangeExhaustedError extends Error {
|
|
36
43
|
constructor(purpose) {
|
|
37
44
|
super(`Port range for purpose '${purpose}' is exhausted`);
|
|
@@ -166,9 +173,43 @@ let PortAllocator = class PortAllocator {
|
|
|
166
173
|
.catch(() => undefined);
|
|
167
174
|
});
|
|
168
175
|
}
|
|
169
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Lease a port `udid` is already serving on. acquire() would refuse it,
|
|
178
|
+
* because its OS probe sees the listener, and that listener is the device's
|
|
179
|
+
* own: iOS attaching to the WDA an Appium session forwards is the case. The
|
|
180
|
+
* lease is what keeps the port from being handed to another device once the
|
|
181
|
+
* listener closes.
|
|
182
|
+
*
|
|
183
|
+
* Returns false, and leaves the row alone, when another udid holds an
|
|
184
|
+
* unexpired lease on the port.
|
|
185
|
+
*/
|
|
186
|
+
claim(purpose_1, udid_1, port_1) {
|
|
187
|
+
return __awaiter(this, arguments, void 0, function* (purpose, udid, port, opts = {}) {
|
|
188
|
+
var _a;
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
const ttlMs = (_a = opts.ttlMs) !== null && _a !== void 0 ? _a : 60 * 60 * 1000;
|
|
191
|
+
const held = yield prisma_1.prisma.portLease.findUnique({ where: { port } });
|
|
192
|
+
if (held && held.leasedToUdid !== udid && held.expiresAt >= now)
|
|
193
|
+
return false;
|
|
194
|
+
const lease = { purpose, leasedToUdid: udid, leasedAt: now, expiresAt: now + ttlMs };
|
|
195
|
+
yield prisma_1.prisma.portLease.upsert({
|
|
196
|
+
where: { port },
|
|
197
|
+
create: Object.assign({ port }, lease),
|
|
198
|
+
update: lease,
|
|
199
|
+
});
|
|
200
|
+
return true;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Release `port` if `udid` holds it. A port number alone doesn't say whose
|
|
205
|
+
* lease it is: a stale session releasing "its" port used to delete whatever
|
|
206
|
+
* lease held that number by then, including another device's live one.
|
|
207
|
+
*/
|
|
208
|
+
release(port, udid) {
|
|
170
209
|
return __awaiter(this, void 0, void 0, function* () {
|
|
171
|
-
yield prisma_1.prisma.portLease
|
|
210
|
+
yield prisma_1.prisma.portLease
|
|
211
|
+
.deleteMany({ where: { port, leasedToUdid: udid } })
|
|
212
|
+
.catch(() => undefined);
|
|
172
213
|
});
|
|
173
214
|
}
|
|
174
215
|
/**
|
|
@@ -195,23 +236,41 @@ let PortAllocator = class PortAllocator {
|
|
|
195
236
|
yield prisma_1.prisma.portLease.deleteMany({ where: { expiresAt: { lt: Date.now() } } });
|
|
196
237
|
});
|
|
197
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Whether nothing listens on `port` on any local address.
|
|
241
|
+
*
|
|
242
|
+
* One bind can't tell. On macOS a bind to 127.0.0.1 succeeds beside a
|
|
243
|
+
* wildcard listener, and the more specific socket then takes every
|
|
244
|
+
* 127.0.0.1 connection. iproxy listens on the IPv6 wildcard, so a
|
|
245
|
+
* 127.0.0.1-only probe called an iPhone's 9100 free, the S9+'s MJPEG server
|
|
246
|
+
* bound 127.0.0.1:9100 over it, and the iPhone's recording showed the S9+.
|
|
247
|
+
*
|
|
248
|
+
* Each listener shape fails at least one of these binds (checked on macOS
|
|
249
|
+
* with Node's default SO_REUSEADDR): 127.0.0.1 → 127.0.0.1, ::1 → ::1,
|
|
250
|
+
* 0.0.0.0 → 0.0.0.0, dual-stack :: → 0.0.0.0 and ::, v6-only :: → ::.
|
|
251
|
+
*/
|
|
198
252
|
isOsFree(port) {
|
|
253
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
254
|
+
let probed = 0;
|
|
255
|
+
// One at a time: probes in flight together can fail on each other (a
|
|
256
|
+
// 0.0.0.0 bind fails beside a :: one).
|
|
257
|
+
for (const host of PROBE_HOSTS) {
|
|
258
|
+
const code = yield this.bindProbe(port, host);
|
|
259
|
+
if (code === null)
|
|
260
|
+
probed++;
|
|
261
|
+
else if (!ADDRESS_UNAVAILABLE.has(code))
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
return probed > 0;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/** Bind `host:port` and close it again. Resolves null on success, else the error code. */
|
|
268
|
+
bindProbe(port, host) {
|
|
199
269
|
return new Promise((resolve) => {
|
|
200
270
|
const server = net_1.default.createServer();
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return;
|
|
205
|
-
settled = true;
|
|
206
|
-
server.removeAllListeners();
|
|
207
|
-
if (ok)
|
|
208
|
-
server.close(() => resolve(true));
|
|
209
|
-
else
|
|
210
|
-
resolve(false);
|
|
211
|
-
};
|
|
212
|
-
server.once('error', () => done(false));
|
|
213
|
-
server.once('listening', () => done(true));
|
|
214
|
-
server.listen(port, '127.0.0.1');
|
|
271
|
+
server.once('error', (err) => { var _a; return resolve((_a = err.code) !== null && _a !== void 0 ? _a : 'UNKNOWN'); });
|
|
272
|
+
server.once('listening', () => server.close(() => resolve(null)));
|
|
273
|
+
server.listen(port, host);
|
|
215
274
|
});
|
|
216
275
|
}
|
|
217
276
|
};
|
|
@@ -63,6 +63,8 @@ const networkAddresses_1 = require("../helpers/networkAddresses");
|
|
|
63
63
|
const logger_1 = __importDefault(require("../logger"));
|
|
64
64
|
const h264StreamWs_1 = require("../app/ws/h264StreamWs");
|
|
65
65
|
const logcatWs_1 = require("../app/ws/logcatWs");
|
|
66
|
+
const nodeSocketRelay_1 = require("../app/ws/nodeSocketRelay");
|
|
67
|
+
const upgradeRouter_1 = require("../app/ws/upgradeRouter");
|
|
66
68
|
const StreamTicketService_1 = require("./token/StreamTicketService");
|
|
67
69
|
const AndroidH264StreamService_1 = __importDefault(require("../device-managers/android/AndroidH264StreamService"));
|
|
68
70
|
const LogcatStreamService_1 = require("../device-managers/android/LogcatStreamService");
|
|
@@ -77,16 +79,16 @@ const config_service_1 = require("../data-service/config-service");
|
|
|
77
79
|
const PluginContext_1 = require("../PluginContext");
|
|
78
80
|
const device_store_1 = require("../data-service/device-store");
|
|
79
81
|
const device_utils_1 = require("../device-utils");
|
|
82
|
+
const localDeviceHosts_1 = require("../device-managers/localDeviceHosts");
|
|
80
83
|
const app_1 = require("../app");
|
|
81
|
-
const wd_command_proxy_1 = require("../proxy/wd-command-proxy");
|
|
82
84
|
const registerCommandAuth_1 = require("../app/registerCommandAuth");
|
|
85
|
+
const defaultGateway_1 = require("../gateway/defaultGateway");
|
|
83
86
|
const appium_adb_1 = require("appium-adb");
|
|
84
87
|
const ChromeDriverManager_1 = __importDefault(require("../device-managers/ChromeDriverManager"));
|
|
85
88
|
const AndroidDeviceManager_1 = __importDefault(require("../device-managers/AndroidDeviceManager"));
|
|
86
89
|
const IOSDeviceManager_1 = __importDefault(require("../device-managers/IOSDeviceManager"));
|
|
87
90
|
const device_managers_1 = require("../device-managers");
|
|
88
91
|
const pluginArgs_1 = require("../data-service/pluginArgs");
|
|
89
|
-
const NodeDevices_1 = __importDefault(require("../device-managers/NodeDevices"));
|
|
90
92
|
const config_1 = require("../config");
|
|
91
93
|
const SocketServer_1 = require("./SocketServer");
|
|
92
94
|
const SocketClient_1 = require("./SocketClient");
|
|
@@ -128,13 +130,17 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
128
130
|
}
|
|
129
131
|
yield this.syncDatabaseAndAIConfig(pluginArgs);
|
|
130
132
|
yield this.initializeCoreSubsystems(pluginArgs, cliArgs.port);
|
|
131
|
-
this.registerRoutes(expressApp, cliArgs, pluginArgs);
|
|
133
|
+
this.registerRoutes(expressApp, httpServer, cliArgs, pluginArgs);
|
|
132
134
|
yield this.bootEmulators(pluginArgs);
|
|
133
135
|
this.registerDependenciesInContainer(pluginArgs, cliArgs, nodeId);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
// The hosts this server files its own phones under. Every "is this phone
|
|
137
|
+
// mine?" below compares against these exactly: a node on this machine
|
|
138
|
+
// shares the IP and differs only by port.
|
|
139
|
+
const localHosts = (0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, cliArgs.port);
|
|
140
|
+
yield this.setupHubOrNode(pluginArgs, cliArgs, httpServer, localHosts);
|
|
141
|
+
yield this.setupMaintenanceCrons(pluginArgs, localHosts);
|
|
136
142
|
// Live H.264 preview WebSocket (Android; feature-flagged in the frontend).
|
|
137
|
-
//
|
|
143
|
+
// A route of the upgrade router: only /stream/h264 upgrades reach it. Ticket-auth'd.
|
|
138
144
|
if (httpServer) {
|
|
139
145
|
(0, h264StreamWs_1.attachH264Ws)(httpServer, {
|
|
140
146
|
redeem: (ticket, udid) => typedi_1.Container.get(StreamTicketService_1.StreamTicketService).redeem(ticket, udid),
|
|
@@ -142,10 +148,12 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
142
148
|
// streaming.androidH264 config itself, so this WS auto-start honours a
|
|
143
149
|
// { source: 'screenrecord' } rollback just like the REST stream/start.
|
|
144
150
|
startStream: (udid) => typedi_1.Container.get(AndroidH264StreamService_1.default).start(udid),
|
|
151
|
+
// A node's phone is streamed by its node and relayed from here.
|
|
152
|
+
nodeSocket: (udid, actor) => (0, nodeSocketRelay_1.openNodeSocket)({ udid, actor, path: 'stream/h264' }),
|
|
145
153
|
});
|
|
146
154
|
// Continuous logcat WebSocket (Android; replaces the 3s dump-poll).
|
|
147
|
-
//
|
|
148
|
-
//
|
|
155
|
+
// A route of the upgrade router: only /logcat upgrades reach it.
|
|
156
|
+
// Ticket-auth'd like h264, plus an ownership check h264
|
|
149
157
|
// doesn't need: logcat routinely carries auth tokens and PII from
|
|
150
158
|
// whatever app is under test, so it's an ownership-checked read, not
|
|
151
159
|
// an open one — see docs/superpowers/specs/2026-08-09-logcat-stream-design.md
|
|
@@ -186,12 +194,22 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
186
194
|
// server-side level filter applies.
|
|
187
195
|
return { mux: yield typedi_1.Container.get(LogcatStreamService_1.LogcatStreamService).start(udid) };
|
|
188
196
|
}),
|
|
197
|
+
// A node's phone: its node streams the log, with the same filter.
|
|
198
|
+
nodeSocket: (udid, actor, filter) => {
|
|
199
|
+
var _a;
|
|
200
|
+
return (0, nodeSocketRelay_1.openNodeSocket)({
|
|
201
|
+
udid,
|
|
202
|
+
actor,
|
|
203
|
+
path: 'logcat',
|
|
204
|
+
query: new URLSearchParams(Object.assign(Object.assign({}, (((_a = filter.levels) === null || _a === void 0 ? void 0 : _a.length) ? { levels: filter.levels.join(',') } : {})), (filter.process ? { process: filter.process } : {}))),
|
|
205
|
+
});
|
|
206
|
+
},
|
|
189
207
|
});
|
|
190
208
|
}
|
|
191
209
|
// Principal Cleaning: Attempt to recover remote sessions before marking as failed
|
|
192
210
|
const { SessionManager } = yield Promise.resolve().then(() => __importStar(require('../sessions/SessionManager')));
|
|
193
211
|
const sessionManager = typedi_1.Container.get(SessionManager);
|
|
194
|
-
const recoveredCount = yield sessionManager.recoverActiveSessions(nodeId, plugin_1.XenonPlugin.nodeBasePath);
|
|
212
|
+
const recoveredCount = yield sessionManager.recoverActiveSessions(nodeId, plugin_1.XenonPlugin.nodeBasePath, (host) => (0, localDeviceHosts_1.isLocalDeviceHost)(localHosts, host));
|
|
195
213
|
const recoveredSessionIds = sessionManager.getAllSessions().map((s) => s.getId());
|
|
196
214
|
if (recoveredCount > 0) {
|
|
197
215
|
logger_1.default.info(`🔄 Successfully recovered ${recoveredCount} remote sessions`);
|
|
@@ -232,9 +250,9 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
232
250
|
const { cleanupZombieSessions } = yield Promise.resolve().then(() => __importStar(require('../dashboard/services/session-service')));
|
|
233
251
|
yield cleanupZombieSessions(recoveredSessionIds);
|
|
234
252
|
// Initial device discovery poll to start managers and trackers
|
|
235
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
253
|
+
yield (0, device_utils_1.updateDeviceList)(localHosts, pluginArgs.hub, pluginArgs.tlsRejectUnauthorized);
|
|
236
254
|
// remove stale devices
|
|
237
|
-
yield (0, device_utils_1.removeStaleDevices)(
|
|
255
|
+
yield (0, device_utils_1.removeStaleDevices)(localHosts, pluginArgs.tlsRejectUnauthorized);
|
|
238
256
|
this.logger.info(`🚀 Xenon will be served at http://${pluginArgs.bindHostOrIp}:${cliArgs.port}/xenon with id ${nodeId}`);
|
|
239
257
|
this.logger.info('You can provide the following URLs to access Xenon:');
|
|
240
258
|
for (const url of (0, networkAddresses_1.listReachableBaseUrls)(cliArgs.port)) {
|
|
@@ -314,7 +332,8 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
314
332
|
yield (0, device_utils_1.initializeStorage)();
|
|
315
333
|
const { runMigrations } = yield Promise.resolve().then(() => __importStar(require('../scripts/run-migrations')));
|
|
316
334
|
yield runMigrations();
|
|
317
|
-
|
|
335
|
+
// A hub keeps its nodes' phones: the sessions on them outlive its restart.
|
|
336
|
+
yield device_store_1.DeviceStoreFactory.getStore().clearStorage((0, localDeviceHosts_1.devicesClearedAtBoot)(pluginArgs, (0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, port)));
|
|
318
337
|
const { bootstrapIdentity } = yield Promise.resolve().then(() => __importStar(require('./identity/bootstrap')));
|
|
319
338
|
yield bootstrapIdentity();
|
|
320
339
|
typedi_1.Container.get(EmailService_1.EmailService).warnIfLogFallbackEnabled();
|
|
@@ -345,15 +364,33 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
345
364
|
typedi_1.Container.set(ARTIFACT_STORE, new FsArtifactStore(config_1.config.recordingsAssetsPath));
|
|
346
365
|
});
|
|
347
366
|
}
|
|
348
|
-
registerRoutes(expressApp, cliArgs, pluginArgs) {
|
|
367
|
+
registerRoutes(expressApp, httpServer, cliArgs, pluginArgs) {
|
|
349
368
|
expressApp.use('/xenon', (0, app_1.createRouter)(pluginArgs));
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
369
|
+
// The session gateway goes in front of Appium's routes: Xenon's own
|
|
370
|
+
// /wd-internal calls (with the per-process secret), per-command auth
|
|
371
|
+
// (XENON_REQUIRE_COMMAND_AUTH), then, on a hub, forwarding of the sessions
|
|
372
|
+
// nodes run, so a hub checks a command before forwarding it and Appium's
|
|
373
|
+
// umbrella never sees a remote session. A node accepts the hub's session
|
|
374
|
+
// token in place of the client's credentials. With it go the filter on
|
|
375
|
+
// Appium's session listing and the guard on session WebSockets (BiDi,
|
|
376
|
+
// driver /ws/session/...), which upgrade outside the routes. One set of
|
|
377
|
+
// deps, so all three share a credential cache. Each throws when the
|
|
378
|
+
// setting is on and it cannot be installed, rather than serve sessions
|
|
379
|
+
// unchecked.
|
|
380
|
+
const commandAuth = (0, registerCommandAuth_1.commandAuthDeps)();
|
|
381
|
+
(0, registerCommandAuth_1.registerSessionGateway)(expressApp, cliArgs, commandAuth, (0, defaultGateway_1.gatewayOptionsFor)(pluginArgs, cliArgs, () => typedi_1.Container.get(PluginContext_1.PluginContext).nodeId));
|
|
382
|
+
(0, registerCommandAuth_1.registerSessionUpgradeGuard)(httpServer, expressApp, cliArgs, commandAuth);
|
|
383
|
+
// One owner per WebSocket upgrade (upgradeRouter.ts): Xenon's H.264,
|
|
384
|
+
// logcat and socket.io paths go to Xenon, everything else to Appium behind
|
|
385
|
+
// the session guard. Installed after the guard, so the guard never sees
|
|
386
|
+
// Xenon's sockets, and before socket.io and the H.264 / logcat sockets
|
|
387
|
+
// attach to it below. Without it, which side got an upgrade depended on
|
|
388
|
+
// the Node version, and each version broke one side.
|
|
389
|
+
if (httpServer) {
|
|
390
|
+
const router = (0, upgradeRouter_1.upgradeRouterFor)(httpServer);
|
|
391
|
+
this.logger.info("WebSocket upgrades: Xenon's own paths go to Xenon; every other path goes to " +
|
|
392
|
+
`${router.describeAppiumPath()}.`);
|
|
393
|
+
}
|
|
357
394
|
}
|
|
358
395
|
bootEmulators(pluginArgs) {
|
|
359
396
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -384,22 +421,18 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
384
421
|
}
|
|
385
422
|
(0, pluginArgs_1.addCLIArgs)(cliArgs);
|
|
386
423
|
}
|
|
387
|
-
setupHubOrNode(pluginArgs, cliArgs, httpServer,
|
|
424
|
+
setupHubOrNode(pluginArgs, cliArgs, httpServer, localHosts) {
|
|
388
425
|
return __awaiter(this, void 0, void 0, function* () {
|
|
389
426
|
const hubArgument = pluginArgs.hub;
|
|
390
427
|
if (hubArgument !== undefined) {
|
|
391
428
|
this.logger.info(`📡 I'm a node and my hub is ${hubArgument}`);
|
|
392
|
-
yield (0, device_utils_1.setupCronUpdateDeviceList)(
|
|
429
|
+
yield (0, device_utils_1.setupCronUpdateDeviceList)(localHosts, hubArgument, pluginArgs.sendNodeDevicesToHubIntervalMs, pluginArgs.tlsRejectUnauthorized);
|
|
393
430
|
// Handle graceful shutdown
|
|
394
431
|
['SIGINT', 'SIGTERM'].forEach((signal) => {
|
|
395
432
|
process.once(signal, () => __awaiter(this, void 0, void 0, function* () {
|
|
396
433
|
logger_1.default.info(`Received ${signal}, unregistering node from hub...`);
|
|
397
434
|
try {
|
|
398
|
-
yield
|
|
399
|
-
tlsRejectUnauthorized: pluginArgs.tlsRejectUnauthorized,
|
|
400
|
-
hubAccessKey: config_1.config.hubAccessKey,
|
|
401
|
-
hubToken: config_1.config.hubToken,
|
|
402
|
-
}).unRegisterNode(pluginArgs.bindHostOrIp);
|
|
435
|
+
yield (0, device_utils_1.unregisterNodeFromHub)(hubArgument, localHosts, pluginArgs.tlsRejectUnauthorized, typedi_1.Container.get(PluginContext_1.PluginContext).nodeId);
|
|
403
436
|
}
|
|
404
437
|
catch (err) {
|
|
405
438
|
logger_1.default.error(`Error during node unregistration: ${err}`);
|
|
@@ -417,7 +450,7 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
417
450
|
// Principal discovery: Background poll to prune stale/offline devices on the Hub itself
|
|
418
451
|
(() => __awaiter(this, void 0, void 0, function* () {
|
|
419
452
|
const { setupCronLocalDiscovery } = yield Promise.resolve().then(() => __importStar(require('../device-utils')));
|
|
420
|
-
yield setupCronLocalDiscovery(
|
|
453
|
+
yield setupCronLocalDiscovery(localHosts, pluginArgs.sendNodeDevicesToHubIntervalMs);
|
|
421
454
|
}))();
|
|
422
455
|
const socketServer = typedi_1.Container.get(SocketServer_1.SocketServer);
|
|
423
456
|
socketServer.initialize(httpServer);
|
|
@@ -426,12 +459,12 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
426
459
|
}
|
|
427
460
|
});
|
|
428
461
|
}
|
|
429
|
-
setupMaintenanceCrons(pluginArgs) {
|
|
462
|
+
setupMaintenanceCrons(pluginArgs, localHosts) {
|
|
430
463
|
return __awaiter(this, void 0, void 0, function* () {
|
|
431
464
|
var _a;
|
|
432
465
|
if (!((_a = pluginArgs.cloud) === null || _a === void 0 ? void 0 : _a.cloudName)) {
|
|
433
466
|
// 1. Check for stale nodes
|
|
434
|
-
yield (0, device_utils_1.setupCronCheckStaleDevices)(pluginArgs.checkStaleDevicesIntervalMs,
|
|
467
|
+
yield (0, device_utils_1.setupCronCheckStaleDevices)(pluginArgs.checkStaleDevicesIntervalMs, localHosts, pluginArgs.tlsRejectUnauthorized);
|
|
435
468
|
// 2. Release blocked devices
|
|
436
469
|
yield (0, device_utils_1.setupCronReleaseBlockedDevices)(pluginArgs.checkBlockedDevicesIntervalMs, pluginArgs.newCommandTimeoutSec);
|
|
437
470
|
// 3. Clean up pending sessions
|