@xenon-device-management/xenon 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/lib/package.json +1 -2
- package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-C-Pw_hvw.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-DmYQ0oSk.js} +1 -1
- package/lib/public/assets/BugReportButton-n8T1dFUp.js +10 -0
- package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-D-rGOBbJ.js} +1 -1
- package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-DExETevp.js} +1 -1
- package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-CCdFlZm6.js} +1 -1
- package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-DiUSOSL-.js} +1 -1
- package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-ZOfpH6gh.js} +1 -1
- package/lib/public/assets/{Modal-CceoMY7D.js → Modal-CZNtoB-1.js} +1 -1
- package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-BKp6h8Jl.js} +1 -1
- package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-Bf881FX4.js} +1 -1
- package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-EUoyizvW.js} +1 -1
- package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-ClT4hf4_.js} +1 -1
- package/lib/public/assets/{Table-LV7zFyZg.js → Table-ucR4bnmc.js} +1 -1
- package/lib/public/assets/{activity-D17TUloi.js → activity-K-hdylwq.js} +1 -1
- package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-BLhdwI3c.js} +1 -1
- package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-BbiN7rBI.js} +1 -1
- package/lib/public/assets/{apps-Dbnzd0fW.js → apps-Dez1petz.js} +1 -1
- package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-UjmFHLAJ.js} +1 -1
- package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-BVyEigbu.js} +1 -1
- package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-B-ZlBof3.js} +1 -1
- package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-DAQjAgUv.js} +2 -2
- package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-CmIPa5JK.js} +1 -1
- package/lib/public/assets/{button-xtAhYR-f.js → button-Dd-IhMC1.js} +1 -1
- package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-CAIeJwa5.js} +1 -1
- package/lib/public/assets/{check-CyuEoKkd.js → check-C4Jrjg8f.js} +1 -1
- package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-BGzlf-4i.js} +1 -1
- package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-_wYqDjDC.js} +1 -1
- package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-Ckn00yMB.js} +1 -1
- package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-pdVkomLa.js} +1 -1
- package/lib/public/assets/{clock-Cd-t7xpe.js → clock-B8vmwkHj.js} +1 -1
- package/lib/public/assets/{copy-C6zerX3I.js → copy-BAqnFI-F.js} +1 -1
- package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CYq_4F_N.js} +3 -3
- package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
- package/lib/public/assets/{download-x9WP7mcL.js → download-B6UEAp_1.js} +1 -1
- package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-CLqNvmrN.js} +1 -1
- package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-Da90BBbT.js} +1 -1
- package/lib/public/assets/{index-BR1wTIPC.js → index-BuIIcmGp.js} +2 -2
- package/lib/public/assets/{index-BvH_ekRy.js → index-MX7IzqyW.js} +1 -1
- package/lib/public/assets/{input-DejcCiTI.js → input-Bq9aY-We.js} +1 -1
- package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CIhXIJyP.js} +1 -1
- package/lib/public/assets/{layers-BKqYLiHE.js → layers-bXjpswQI.js} +1 -1
- package/lib/public/assets/{lock-LFsFhc17.js → lock-pJ72y5Fy.js} +1 -1
- package/lib/public/assets/{login-x2pkzt-t.js → login-UOcHALV2.js} +1 -1
- package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-XnJgp5yb.js} +1 -1
- package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-QkAhI1Jx.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-B42Da0m4.js} +1 -1
- package/lib/public/assets/{network-Z_zBhpsD.js → network-CKVqcyp6.js} +1 -1
- package/lib/public/assets/{overview-BY5uv08S.js → overview-BORGkN3N.js} +1 -1
- package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CKWFOjOI.js} +1 -1
- package/lib/public/assets/{plus-BTD0dnQ0.js → plus-BpFKVdC8.js} +1 -1
- package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-CcbKBicY.js} +1 -1
- package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BA0W9bxl.js} +1 -1
- package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Cp7ZBq3s.js} +1 -1
- package/lib/public/assets/{select-Uc9E5pye.js → select-DIZWIoVR.js} +1 -1
- package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C_4YuvAp.js} +1 -1
- package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CXjD_Xth.js} +1 -1
- package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CTqIHeoG.js} +1 -1
- package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-B0KYkk8z.js} +1 -1
- package/lib/public/assets/{settings-CkSWiZq8.js → settings-BcRBKUXd.js} +1 -1
- package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-Dh2PXSf_.js} +1 -1
- package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-_sXqZztt.js} +1 -1
- package/lib/public/assets/{teams-PpoEQz31.js → teams-DGHT8MKm.js} +1 -1
- package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV88cV3Z.js} +1 -1
- package/lib/public/assets/{upload-C3i7sAZl.js → upload-CIDH9wkj.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-DYGuSNwl.js} +1 -1
- package/lib/public/assets/{users-CEJ3fC78.js → users-7yviqflc.js} +1 -1
- package/lib/public/assets/{users-Drd7ifJx.js → users-BUE7MoTq.js} +1 -1
- package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-BTRxtlGo.js} +1 -1
- package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-DMKD9Pgm.js} +1 -1
- package/lib/public/assets/{zap-4yN8Jdfw.js → zap-CWE94qcK.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 +57 -13
- package/lib/src/app/routers/grid.js +28 -8
- package/lib/src/app/ws/h264StreamWs.js +13 -8
- package/lib/src/app/ws/logcatWs.js +12 -7
- 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 +81 -10
- package/lib/src/data-service/deviceClaims.js +98 -0
- package/lib/src/data-service/deviceFieldOwners.js +32 -10
- package/lib/src/data-service/prisma-store.js +94 -31
- package/lib/src/device-managers/AndroidDeviceManager.js +13 -15
- 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 +3 -3
- package/lib/src/device-managers/android/AndroidStreamService.js +6 -5
- 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 +158 -38
- 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/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 +50 -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/lease/LeaseOrphanSweeper.js +32 -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/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/unit/AndroidDeviceManager.spec.js +8 -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 +6 -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 +18 -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 +28 -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 +291 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
- package/lib/test/unit/hub-node-race.spec.js +401 -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 +2 -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 +92 -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 +4 -1
- package/lib/test/unit/loopback-request.spec.js +81 -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/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 +15 -7
- package/lib/test/unit/stream-own-device-row.spec.js +164 -0
- package/lib/test/unit/stream-stop-ownership.spec.js +13 -5
- package/lib/test/unit/stream-stop-recording.spec.js +13 -5
- package/lib/test/unit/stream-ticket-identity.spec.js +15 -5
- 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 +1 -2
- 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,7 @@ 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 upgradeRouter_1 = require("../app/ws/upgradeRouter");
|
|
66
67
|
const StreamTicketService_1 = require("./token/StreamTicketService");
|
|
67
68
|
const AndroidH264StreamService_1 = __importDefault(require("../device-managers/android/AndroidH264StreamService"));
|
|
68
69
|
const LogcatStreamService_1 = require("../device-managers/android/LogcatStreamService");
|
|
@@ -77,16 +78,16 @@ const config_service_1 = require("../data-service/config-service");
|
|
|
77
78
|
const PluginContext_1 = require("../PluginContext");
|
|
78
79
|
const device_store_1 = require("../data-service/device-store");
|
|
79
80
|
const device_utils_1 = require("../device-utils");
|
|
81
|
+
const localDeviceHosts_1 = require("../device-managers/localDeviceHosts");
|
|
80
82
|
const app_1 = require("../app");
|
|
81
|
-
const wd_command_proxy_1 = require("../proxy/wd-command-proxy");
|
|
82
83
|
const registerCommandAuth_1 = require("../app/registerCommandAuth");
|
|
84
|
+
const defaultGateway_1 = require("../gateway/defaultGateway");
|
|
83
85
|
const appium_adb_1 = require("appium-adb");
|
|
84
86
|
const ChromeDriverManager_1 = __importDefault(require("../device-managers/ChromeDriverManager"));
|
|
85
87
|
const AndroidDeviceManager_1 = __importDefault(require("../device-managers/AndroidDeviceManager"));
|
|
86
88
|
const IOSDeviceManager_1 = __importDefault(require("../device-managers/IOSDeviceManager"));
|
|
87
89
|
const device_managers_1 = require("../device-managers");
|
|
88
90
|
const pluginArgs_1 = require("../data-service/pluginArgs");
|
|
89
|
-
const NodeDevices_1 = __importDefault(require("../device-managers/NodeDevices"));
|
|
90
91
|
const config_1 = require("../config");
|
|
91
92
|
const SocketServer_1 = require("./SocketServer");
|
|
92
93
|
const SocketClient_1 = require("./SocketClient");
|
|
@@ -128,13 +129,17 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
128
129
|
}
|
|
129
130
|
yield this.syncDatabaseAndAIConfig(pluginArgs);
|
|
130
131
|
yield this.initializeCoreSubsystems(pluginArgs, cliArgs.port);
|
|
131
|
-
this.registerRoutes(expressApp, cliArgs, pluginArgs);
|
|
132
|
+
this.registerRoutes(expressApp, httpServer, cliArgs, pluginArgs);
|
|
132
133
|
yield this.bootEmulators(pluginArgs);
|
|
133
134
|
this.registerDependenciesInContainer(pluginArgs, cliArgs, nodeId);
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
// The hosts this server files its own phones under. Every "is this phone
|
|
136
|
+
// mine?" below compares against these exactly: a node on this machine
|
|
137
|
+
// shares the IP and differs only by port.
|
|
138
|
+
const localHosts = (0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, cliArgs.port);
|
|
139
|
+
yield this.setupHubOrNode(pluginArgs, cliArgs, httpServer, localHosts);
|
|
140
|
+
yield this.setupMaintenanceCrons(pluginArgs, localHosts);
|
|
136
141
|
// Live H.264 preview WebSocket (Android; feature-flagged in the frontend).
|
|
137
|
-
//
|
|
142
|
+
// A route of the upgrade router: only /stream/h264 upgrades reach it. Ticket-auth'd.
|
|
138
143
|
if (httpServer) {
|
|
139
144
|
(0, h264StreamWs_1.attachH264Ws)(httpServer, {
|
|
140
145
|
redeem: (ticket, udid) => typedi_1.Container.get(StreamTicketService_1.StreamTicketService).redeem(ticket, udid),
|
|
@@ -144,8 +149,8 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
144
149
|
startStream: (udid) => typedi_1.Container.get(AndroidH264StreamService_1.default).start(udid),
|
|
145
150
|
});
|
|
146
151
|
// Continuous logcat WebSocket (Android; replaces the 3s dump-poll).
|
|
147
|
-
//
|
|
148
|
-
//
|
|
152
|
+
// A route of the upgrade router: only /logcat upgrades reach it.
|
|
153
|
+
// Ticket-auth'd like h264, plus an ownership check h264
|
|
149
154
|
// doesn't need: logcat routinely carries auth tokens and PII from
|
|
150
155
|
// whatever app is under test, so it's an ownership-checked read, not
|
|
151
156
|
// an open one — see docs/superpowers/specs/2026-08-09-logcat-stream-design.md
|
|
@@ -191,7 +196,7 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
191
196
|
// Principal Cleaning: Attempt to recover remote sessions before marking as failed
|
|
192
197
|
const { SessionManager } = yield Promise.resolve().then(() => __importStar(require('../sessions/SessionManager')));
|
|
193
198
|
const sessionManager = typedi_1.Container.get(SessionManager);
|
|
194
|
-
const recoveredCount = yield sessionManager.recoverActiveSessions(nodeId, plugin_1.XenonPlugin.nodeBasePath);
|
|
199
|
+
const recoveredCount = yield sessionManager.recoverActiveSessions(nodeId, plugin_1.XenonPlugin.nodeBasePath, (host) => (0, localDeviceHosts_1.isLocalDeviceHost)(localHosts, host));
|
|
195
200
|
const recoveredSessionIds = sessionManager.getAllSessions().map((s) => s.getId());
|
|
196
201
|
if (recoveredCount > 0) {
|
|
197
202
|
logger_1.default.info(`🔄 Successfully recovered ${recoveredCount} remote sessions`);
|
|
@@ -232,9 +237,9 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
232
237
|
const { cleanupZombieSessions } = yield Promise.resolve().then(() => __importStar(require('../dashboard/services/session-service')));
|
|
233
238
|
yield cleanupZombieSessions(recoveredSessionIds);
|
|
234
239
|
// Initial device discovery poll to start managers and trackers
|
|
235
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
240
|
+
yield (0, device_utils_1.updateDeviceList)(localHosts, pluginArgs.hub, pluginArgs.tlsRejectUnauthorized);
|
|
236
241
|
// remove stale devices
|
|
237
|
-
yield (0, device_utils_1.removeStaleDevices)(
|
|
242
|
+
yield (0, device_utils_1.removeStaleDevices)(localHosts, pluginArgs.tlsRejectUnauthorized);
|
|
238
243
|
this.logger.info(`🚀 Xenon will be served at http://${pluginArgs.bindHostOrIp}:${cliArgs.port}/xenon with id ${nodeId}`);
|
|
239
244
|
this.logger.info('You can provide the following URLs to access Xenon:');
|
|
240
245
|
for (const url of (0, networkAddresses_1.listReachableBaseUrls)(cliArgs.port)) {
|
|
@@ -314,7 +319,8 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
314
319
|
yield (0, device_utils_1.initializeStorage)();
|
|
315
320
|
const { runMigrations } = yield Promise.resolve().then(() => __importStar(require('../scripts/run-migrations')));
|
|
316
321
|
yield runMigrations();
|
|
317
|
-
|
|
322
|
+
// A hub keeps its nodes' phones: the sessions on them outlive its restart.
|
|
323
|
+
yield device_store_1.DeviceStoreFactory.getStore().clearStorage((0, localDeviceHosts_1.devicesClearedAtBoot)(pluginArgs, (0, localDeviceHosts_1.localDeviceHosts)(pluginArgs, port)));
|
|
318
324
|
const { bootstrapIdentity } = yield Promise.resolve().then(() => __importStar(require('./identity/bootstrap')));
|
|
319
325
|
yield bootstrapIdentity();
|
|
320
326
|
typedi_1.Container.get(EmailService_1.EmailService).warnIfLogFallbackEnabled();
|
|
@@ -345,15 +351,33 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
345
351
|
typedi_1.Container.set(ARTIFACT_STORE, new FsArtifactStore(config_1.config.recordingsAssetsPath));
|
|
346
352
|
});
|
|
347
353
|
}
|
|
348
|
-
registerRoutes(expressApp, cliArgs, pluginArgs) {
|
|
354
|
+
registerRoutes(expressApp, httpServer, cliArgs, pluginArgs) {
|
|
349
355
|
expressApp.use('/xenon', (0, app_1.createRouter)(pluginArgs));
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
356
|
+
// The session gateway goes in front of Appium's routes: Xenon's own
|
|
357
|
+
// /wd-internal calls (with the per-process secret), per-command auth
|
|
358
|
+
// (XENON_REQUIRE_COMMAND_AUTH), then, on a hub, forwarding of the sessions
|
|
359
|
+
// nodes run, so a hub checks a command before forwarding it and Appium's
|
|
360
|
+
// umbrella never sees a remote session. A node accepts the hub's session
|
|
361
|
+
// token in place of the client's credentials. With it go the filter on
|
|
362
|
+
// Appium's session listing and the guard on session WebSockets (BiDi,
|
|
363
|
+
// driver /ws/session/...), which upgrade outside the routes. One set of
|
|
364
|
+
// deps, so all three share a credential cache. Each throws when the
|
|
365
|
+
// setting is on and it cannot be installed, rather than serve sessions
|
|
366
|
+
// unchecked.
|
|
367
|
+
const commandAuth = (0, registerCommandAuth_1.commandAuthDeps)();
|
|
368
|
+
(0, registerCommandAuth_1.registerSessionGateway)(expressApp, cliArgs, commandAuth, (0, defaultGateway_1.gatewayOptionsFor)(pluginArgs, cliArgs, () => typedi_1.Container.get(PluginContext_1.PluginContext).nodeId));
|
|
369
|
+
(0, registerCommandAuth_1.registerSessionUpgradeGuard)(httpServer, expressApp, cliArgs, commandAuth);
|
|
370
|
+
// One owner per WebSocket upgrade (upgradeRouter.ts): Xenon's H.264,
|
|
371
|
+
// logcat and socket.io paths go to Xenon, everything else to Appium behind
|
|
372
|
+
// the session guard. Installed after the guard, so the guard never sees
|
|
373
|
+
// Xenon's sockets, and before socket.io and the H.264 / logcat sockets
|
|
374
|
+
// attach to it below. Without it, which side got an upgrade depended on
|
|
375
|
+
// the Node version, and each version broke one side.
|
|
376
|
+
if (httpServer) {
|
|
377
|
+
const router = (0, upgradeRouter_1.upgradeRouterFor)(httpServer);
|
|
378
|
+
this.logger.info("WebSocket upgrades: Xenon's own paths go to Xenon; every other path goes to " +
|
|
379
|
+
`${router.describeAppiumPath()}.`);
|
|
380
|
+
}
|
|
357
381
|
}
|
|
358
382
|
bootEmulators(pluginArgs) {
|
|
359
383
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -384,22 +408,18 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
384
408
|
}
|
|
385
409
|
(0, pluginArgs_1.addCLIArgs)(cliArgs);
|
|
386
410
|
}
|
|
387
|
-
setupHubOrNode(pluginArgs, cliArgs, httpServer,
|
|
411
|
+
setupHubOrNode(pluginArgs, cliArgs, httpServer, localHosts) {
|
|
388
412
|
return __awaiter(this, void 0, void 0, function* () {
|
|
389
413
|
const hubArgument = pluginArgs.hub;
|
|
390
414
|
if (hubArgument !== undefined) {
|
|
391
415
|
this.logger.info(`📡 I'm a node and my hub is ${hubArgument}`);
|
|
392
|
-
yield (0, device_utils_1.setupCronUpdateDeviceList)(
|
|
416
|
+
yield (0, device_utils_1.setupCronUpdateDeviceList)(localHosts, hubArgument, pluginArgs.sendNodeDevicesToHubIntervalMs, pluginArgs.tlsRejectUnauthorized);
|
|
393
417
|
// Handle graceful shutdown
|
|
394
418
|
['SIGINT', 'SIGTERM'].forEach((signal) => {
|
|
395
419
|
process.once(signal, () => __awaiter(this, void 0, void 0, function* () {
|
|
396
420
|
logger_1.default.info(`Received ${signal}, unregistering node from hub...`);
|
|
397
421
|
try {
|
|
398
|
-
yield
|
|
399
|
-
tlsRejectUnauthorized: pluginArgs.tlsRejectUnauthorized,
|
|
400
|
-
hubAccessKey: config_1.config.hubAccessKey,
|
|
401
|
-
hubToken: config_1.config.hubToken,
|
|
402
|
-
}).unRegisterNode(pluginArgs.bindHostOrIp);
|
|
422
|
+
yield (0, device_utils_1.unregisterNodeFromHub)(hubArgument, localHosts, pluginArgs.tlsRejectUnauthorized, typedi_1.Container.get(PluginContext_1.PluginContext).nodeId);
|
|
403
423
|
}
|
|
404
424
|
catch (err) {
|
|
405
425
|
logger_1.default.error(`Error during node unregistration: ${err}`);
|
|
@@ -417,7 +437,7 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
417
437
|
// Principal discovery: Background poll to prune stale/offline devices on the Hub itself
|
|
418
438
|
(() => __awaiter(this, void 0, void 0, function* () {
|
|
419
439
|
const { setupCronLocalDiscovery } = yield Promise.resolve().then(() => __importStar(require('../device-utils')));
|
|
420
|
-
yield setupCronLocalDiscovery(
|
|
440
|
+
yield setupCronLocalDiscovery(localHosts, pluginArgs.sendNodeDevicesToHubIntervalMs);
|
|
421
441
|
}))();
|
|
422
442
|
const socketServer = typedi_1.Container.get(SocketServer_1.SocketServer);
|
|
423
443
|
socketServer.initialize(httpServer);
|
|
@@ -426,12 +446,12 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
426
446
|
}
|
|
427
447
|
});
|
|
428
448
|
}
|
|
429
|
-
setupMaintenanceCrons(pluginArgs) {
|
|
449
|
+
setupMaintenanceCrons(pluginArgs, localHosts) {
|
|
430
450
|
return __awaiter(this, void 0, void 0, function* () {
|
|
431
451
|
var _a;
|
|
432
452
|
if (!((_a = pluginArgs.cloud) === null || _a === void 0 ? void 0 : _a.cloudName)) {
|
|
433
453
|
// 1. Check for stale nodes
|
|
434
|
-
yield (0, device_utils_1.setupCronCheckStaleDevices)(pluginArgs.checkStaleDevicesIntervalMs,
|
|
454
|
+
yield (0, device_utils_1.setupCronCheckStaleDevices)(pluginArgs.checkStaleDevicesIntervalMs, localHosts, pluginArgs.tlsRejectUnauthorized);
|
|
435
455
|
// 2. Release blocked devices
|
|
436
456
|
yield (0, device_utils_1.setupCronReleaseBlockedDevices)(pluginArgs.checkBlockedDevicesIntervalMs, pluginArgs.newCommandTimeoutSec);
|
|
437
457
|
// 3. Clean up pending sessions
|