@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
|
@@ -52,6 +52,7 @@ const semver = __importStar(require("semver"));
|
|
|
52
52
|
const logger_1 = __importDefault(require("../logger"));
|
|
53
53
|
const deviceColumns_1 = require("./deviceColumns");
|
|
54
54
|
const deviceFieldOwners_1 = require("./deviceFieldOwners");
|
|
55
|
+
const deviceClaims_1 = require("./deviceClaims");
|
|
55
56
|
/** Logged once per key, so a chatty node doesn't flood the log. */
|
|
56
57
|
const droppedDeviceKeys = new Set();
|
|
57
58
|
class PrismaDeviceStore {
|
|
@@ -75,8 +76,8 @@ class PrismaDeviceStore {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
toIDevice(device) {
|
|
78
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
79
|
-
return Object.assign(Object.assign({}, device), { cloud: this.safeParse(device.cloud, 'cloud', device.udid), capability: this.safeParse(device.capability, 'capability', device.udid), chromeDriverPath: this.safeParse(device.chromeDriverPath, 'chromeDriverPath', device.udid), tags: this.safeParse(device.tags, 'tags', device.udid), platform: (device.platform || 'android'), name: device.name || 'unknown', state: device.state || 'available', sdk: device.sdk || 'unknown', deviceType: device.deviceType || 'real', busy: (_a = device.busy) !== null && _a !== void 0 ? _a : false, userBlocked: (_b = device.userBlocked) !== null && _b !== void 0 ? _b : false, realDevice: (_c = device.realDevice) !== null && _c !== void 0 ? _c : true, lastHealthCheckAt: (_d = device.lastHealthCheckAt) !== null && _d !== void 0 ? _d : undefined, healthStatus: (_e = device.healthStatus) !== null && _e !== void 0 ? _e : 'Healthy', healthCheckError: (_f = device.healthCheckError) !== null && _f !== void 0 ? _f : undefined, batteryLevel: (_g = device.batteryLevel) !== null && _g !== void 0 ? _g : undefined, thermalStatus: (_h = device.thermalStatus) !== null && _h !== void 0 ? _h : undefined, storageFree: (_j = device.storageFree) !== null && _j !== void 0 ? _j : undefined, sessionProgress: (_k = device.sessionProgress) !== null && _k !== void 0 ? _k : '', totalHealedCount: (_l = device.totalHealedCount) !== null && _l !== void 0 ? _l : 0 });
|
|
79
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
80
|
+
return Object.assign(Object.assign({}, device), { cloud: this.safeParse(device.cloud, 'cloud', device.udid), capability: this.safeParse(device.capability, 'capability', device.udid), chromeDriverPath: this.safeParse(device.chromeDriverPath, 'chromeDriverPath', device.udid), tags: this.safeParse(device.tags, 'tags', device.udid), platform: (device.platform || 'android'), name: device.name || 'unknown', state: device.state || 'available', sdk: device.sdk || 'unknown', deviceType: device.deviceType || 'real', busy: (_a = device.busy) !== null && _a !== void 0 ? _a : false, userBlocked: (_b = device.userBlocked) !== null && _b !== void 0 ? _b : false, realDevice: (_c = device.realDevice) !== null && _c !== void 0 ? _c : true, lastHealthCheckAt: (_d = device.lastHealthCheckAt) !== null && _d !== void 0 ? _d : undefined, healthStatus: (_e = device.healthStatus) !== null && _e !== void 0 ? _e : 'Healthy', healthCheckError: (_f = device.healthCheckError) !== null && _f !== void 0 ? _f : undefined, batteryLevel: (_g = device.batteryLevel) !== null && _g !== void 0 ? _g : undefined, thermalStatus: (_h = device.thermalStatus) !== null && _h !== void 0 ? _h : undefined, storageFree: (_j = device.storageFree) !== null && _j !== void 0 ? _j : undefined, sessionProgress: (_k = device.sessionProgress) !== null && _k !== void 0 ? _k : '', totalHealedCount: (_l = device.totalHealedCount) !== null && _l !== void 0 ? _l : 0, nodeBusy: (_m = device.nodeBusy) !== null && _m !== void 0 ? _m : false });
|
|
80
81
|
}
|
|
81
82
|
fromIDevice(device) {
|
|
82
83
|
const data = Object.assign({}, device);
|
|
@@ -208,17 +209,12 @@ class PrismaDeviceStore {
|
|
|
208
209
|
logger_1.default.debug(`[PrismaStore] Update device ${udid} at ${host}: ${result.count} records affected`);
|
|
209
210
|
});
|
|
210
211
|
}
|
|
211
|
-
|
|
212
|
+
touchSession(sessionId, at) {
|
|
212
213
|
return __awaiter(this, void 0, void 0, function* () {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
yield this.prisma.device.update({
|
|
218
|
-
where: { udid_host: { udid: d.udid, host: d.host } },
|
|
219
|
-
data: this.fromIDevice(idv),
|
|
220
|
-
});
|
|
221
|
-
}
|
|
214
|
+
yield this.prisma.device.updateMany({
|
|
215
|
+
where: { session_id: sessionId },
|
|
216
|
+
data: { lastCmdExecutedAt: at },
|
|
217
|
+
});
|
|
222
218
|
});
|
|
223
219
|
}
|
|
224
220
|
addDevices(devices_1) {
|
|
@@ -232,31 +228,63 @@ class PrismaDeviceStore {
|
|
|
232
228
|
const known = new Set(existing.map((d) => `${d.udid}@${d.host}`));
|
|
233
229
|
const added = [];
|
|
234
230
|
for (const device of devices) {
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
231
|
+
const d = options.nodeReport
|
|
232
|
+
? yield this.addNodeReport(device)
|
|
233
|
+
: yield this.prisma.device.upsert({
|
|
234
|
+
// Use upsert to avoid race conditions and unique constraint errors. A
|
|
235
|
+
// phone already here gets only its discovery columns (AddDevicesOptions).
|
|
236
|
+
where: { udid_host: { udid: device.udid, host: device.host } },
|
|
237
|
+
update: (0, deviceFieldOwners_1.pickDiscoveryFields)(this.fromIDevice(device)),
|
|
238
|
+
create: this.fromIDevice(device),
|
|
239
|
+
});
|
|
243
240
|
if (!known.has(`${device.udid}@${device.host}`))
|
|
244
241
|
added.push(this.toIDevice(d));
|
|
245
242
|
}
|
|
246
243
|
return added;
|
|
247
244
|
});
|
|
248
245
|
}
|
|
249
|
-
|
|
246
|
+
/**
|
|
247
|
+
* A node's report of one phone (AddDevicesOptions.nodeReport). The node's
|
|
248
|
+
* `busy` goes to `nodeBusy`. Busy there makes the phone busy here; free
|
|
249
|
+
* there frees it only where this hub holds no claim, in one conditional
|
|
250
|
+
* update, so a report the node sent before the hub claimed the phone can't
|
|
251
|
+
* undo the claim.
|
|
252
|
+
*/
|
|
253
|
+
addNodeReport(device) {
|
|
250
254
|
return __awaiter(this, void 0, void 0, function* () {
|
|
255
|
+
const { udid, host } = device;
|
|
256
|
+
const nodeBusy = device.busy === true;
|
|
257
|
+
const reported = Object.assign(Object.assign({}, (0, deviceFieldOwners_1.pickNodeReportFields)(this.fromIDevice(device))), { nodeBusy });
|
|
258
|
+
const row = yield this.prisma.device.upsert({
|
|
259
|
+
where: { udid_host: { udid, host } },
|
|
260
|
+
update: nodeBusy ? Object.assign(Object.assign({}, reported), { busy: true }) : reported,
|
|
261
|
+
create: Object.assign(Object.assign({}, reported), { udid, host, busy: nodeBusy }),
|
|
262
|
+
});
|
|
263
|
+
if (nodeBusy)
|
|
264
|
+
return row;
|
|
265
|
+
// Nothing of the hub's may hold it either: a claim, or a hold such as a
|
|
266
|
+
// preview or a recording (session_id), exactly as a release checks.
|
|
267
|
+
yield this.prisma.device.updateMany({
|
|
268
|
+
where: Object.assign({ udid, host, busy: true }, deviceClaims_1.UNHELD),
|
|
269
|
+
data: { busy: false },
|
|
270
|
+
});
|
|
271
|
+
return row;
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
removeDevices(filter_1) {
|
|
275
|
+
return __awaiter(this, arguments, void 0, function* (filter, options = {}) {
|
|
251
276
|
// Principal Fix: Support partial host matching (e.g., "192.168.0.100" should match "http://192.168.0.100:4723")
|
|
252
277
|
// This is necessary because the device tracker passes the raw IP, not the full URL.
|
|
278
|
+
// `exactHost` turns it off, for a caller that names rows by their own host.
|
|
253
279
|
const whereClause = {};
|
|
254
280
|
if (filter.udid) {
|
|
255
281
|
whereClause.udid = filter.udid;
|
|
256
282
|
}
|
|
257
283
|
if (filter.host) {
|
|
258
284
|
// Use Prisma's `contains` for partial matching if host doesn't look like a full URL
|
|
259
|
-
if (
|
|
285
|
+
if (options.exactHost ||
|
|
286
|
+
filter.host.startsWith('http://') ||
|
|
287
|
+
filter.host.startsWith('https://')) {
|
|
260
288
|
whereClause.host = filter.host;
|
|
261
289
|
}
|
|
262
290
|
else {
|
|
@@ -266,9 +294,9 @@ class PrismaDeviceStore {
|
|
|
266
294
|
yield this.prisma.device.deleteMany({ where: whereClause });
|
|
267
295
|
});
|
|
268
296
|
}
|
|
269
|
-
clearStorage() {
|
|
297
|
+
clearStorage(onlyHosts) {
|
|
270
298
|
return __awaiter(this, void 0, void 0, function* () {
|
|
271
|
-
yield this.prisma.device.deleteMany();
|
|
299
|
+
yield this.prisma.device.deleteMany(onlyHosts ? { where: { host: { in: [...onlyHosts] } } } : undefined);
|
|
272
300
|
});
|
|
273
301
|
}
|
|
274
302
|
findDevice(filter) {
|
|
@@ -283,8 +311,8 @@ class PrismaDeviceStore {
|
|
|
283
311
|
return devices.map((d) => this.toIDevice(d));
|
|
284
312
|
});
|
|
285
313
|
}
|
|
286
|
-
findAndLockDevice(
|
|
287
|
-
return __awaiter(this,
|
|
314
|
+
findAndLockDevice(filterOptions_1) {
|
|
315
|
+
return __awaiter(this, arguments, void 0, function* (filterOptions, options = {}) {
|
|
288
316
|
// Phase 2: exclude devices held by an active lease.
|
|
289
317
|
const activeLeases = yield this.prisma.lease.findMany({
|
|
290
318
|
where: { status: 'active' },
|
|
@@ -298,6 +326,7 @@ class PrismaDeviceStore {
|
|
|
298
326
|
if (blockedKeys.has(`${device.udid}@${device.host}`)) {
|
|
299
327
|
continue;
|
|
300
328
|
}
|
|
329
|
+
const now = Date.now();
|
|
301
330
|
const result = yield this.prisma.device.updateMany({
|
|
302
331
|
where: {
|
|
303
332
|
udid: device.udid,
|
|
@@ -305,11 +334,7 @@ class PrismaDeviceStore {
|
|
|
305
334
|
busy: false, // Double check it's still free
|
|
306
335
|
offline: false,
|
|
307
336
|
},
|
|
308
|
-
data: {
|
|
309
|
-
busy: true,
|
|
310
|
-
lastCmdExecutedAt: Date.now(),
|
|
311
|
-
sessionStartTime: Date.now(),
|
|
312
|
-
},
|
|
337
|
+
data: Object.assign({ busy: true, lastCmdExecutedAt: now, sessionStartTime: now }, (options.claim ? { claimedAt: now, claimSessionId: null } : {})),
|
|
313
338
|
});
|
|
314
339
|
if (result.count === 1) {
|
|
315
340
|
logger_1.default.info(`[PrismaStore] Successfully locked device ${device.udid} atomically`);
|
|
@@ -323,6 +348,52 @@ class PrismaDeviceStore {
|
|
|
323
348
|
return null;
|
|
324
349
|
});
|
|
325
350
|
}
|
|
351
|
+
claimForSession(udid, host, claimedAt, sessionId, update) {
|
|
352
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
353
|
+
// The pending claim this phone was allocated with, or none left at all
|
|
354
|
+
// (released while the session was created, and not taken since).
|
|
355
|
+
const ours = claimedAt == null ? [{ claimedAt: null }] : [{ claimedAt }, { claimedAt: null }];
|
|
356
|
+
const result = yield this.prisma.device.updateMany({
|
|
357
|
+
where: { udid, host, claimSessionId: null, OR: ours },
|
|
358
|
+
data: Object.assign(Object.assign({}, this.fromIDevice(update)), { busy: true, claimSessionId: sessionId, claimedAt: claimedAt !== null && claimedAt !== void 0 ? claimedAt : Date.now() }),
|
|
359
|
+
});
|
|
360
|
+
return result.count > 0;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
releaseClaim(udid, host, ref, update) {
|
|
364
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
365
|
+
const ended = yield this.prisma.device.updateMany({
|
|
366
|
+
where: Object.assign({ udid, host }, (0, deviceClaims_1.claimWhere)(ref)),
|
|
367
|
+
data: Object.assign(Object.assign({}, this.fromIDevice(update)), deviceClaims_1.CLAIM_RESET),
|
|
368
|
+
});
|
|
369
|
+
if (ended.count === 0)
|
|
370
|
+
return false;
|
|
371
|
+
// Not `busy: false` with the step above: a node reporting the phone busy,
|
|
372
|
+
// or a hold taken since, keeps it busy.
|
|
373
|
+
yield this.prisma.device.updateMany({
|
|
374
|
+
where: Object.assign({ udid, host, busy: true }, deviceClaims_1.UNHELD),
|
|
375
|
+
data: { busy: false },
|
|
376
|
+
});
|
|
377
|
+
return true;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
releaseLeaseLock(udid, host) {
|
|
381
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
382
|
+
const cleared = yield this.prisma.device.updateMany({
|
|
383
|
+
where: Object.assign({ udid, host, busy: true }, deviceClaims_1.UNHELD),
|
|
384
|
+
data: { busy: false },
|
|
385
|
+
});
|
|
386
|
+
return cleared.count > 0;
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
markNodeBusy(udid, host) {
|
|
390
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
391
|
+
yield this.prisma.device.updateMany({
|
|
392
|
+
where: { udid, host },
|
|
393
|
+
data: { nodeBusy: true, busy: true },
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
}
|
|
326
397
|
resetMetrics() {
|
|
327
398
|
return __awaiter(this, void 0, void 0, function* () {
|
|
328
399
|
yield this.prisma.device.updateMany({
|
|
@@ -54,6 +54,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
54
54
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
55
55
|
};
|
|
56
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
exports.includesAndroidDevice = includesAndroidDevice;
|
|
57
58
|
const displayState_1 = require("./android/displayState");
|
|
58
59
|
const networkAddresses_1 = require("../helpers/networkAddresses");
|
|
59
60
|
const appium_adb_1 = require("appium-adb");
|
|
@@ -77,8 +78,24 @@ const Devices_1 = __importDefault(require("./cloud/Devices"));
|
|
|
77
78
|
const DeviceLockManager_1 = require("./android/DeviceLockManager");
|
|
78
79
|
const androidIdentity_1 = require("./android/androidIdentity");
|
|
79
80
|
const deviceIdentity_1 = require("./deviceIdentity");
|
|
81
|
+
const localDeviceHosts_1 = require("./localDeviceHosts");
|
|
80
82
|
const PluginContext_1 = require("../PluginContext");
|
|
81
83
|
const typedi_2 = require("typedi");
|
|
84
|
+
/**
|
|
85
|
+
* Whether a server set to `androidDeviceType` serves this phone: real phones,
|
|
86
|
+
* emulators ('simulated'), or both, and with `bootedEmulators` only the
|
|
87
|
+
* emulators that are up. Discovery and a plugged phone (onDeviceAdded) both
|
|
88
|
+
* ask this, so a phone the server excludes is never added by either.
|
|
89
|
+
*/
|
|
90
|
+
function includesAndroidDevice(device, androidDeviceType, bootedEmulators) {
|
|
91
|
+
if (androidDeviceType === 'real')
|
|
92
|
+
return device.deviceType === 'real';
|
|
93
|
+
if (androidDeviceType === 'simulated' && device.deviceType !== 'emulator')
|
|
94
|
+
return false;
|
|
95
|
+
if (bootedEmulators && device.deviceType === 'emulator')
|
|
96
|
+
return device.state === 'device';
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
82
99
|
let AndroidDeviceManager = class AndroidDeviceManager {
|
|
83
100
|
constructor(context) {
|
|
84
101
|
this.context = context;
|
|
@@ -148,33 +165,7 @@ let AndroidDeviceManager = class AndroidDeviceManager {
|
|
|
148
165
|
devices = yield this.fetchAndroidDevices(existingDeviceDetails, this.pluginArgs);
|
|
149
166
|
logger_1.default.info(`Found ${devices.length} android devices`);
|
|
150
167
|
}
|
|
151
|
-
|
|
152
|
-
return devices.filter((device) => {
|
|
153
|
-
console.log(`Filtering device ${device.udid}, type: ${device.deviceType}, expected real`);
|
|
154
|
-
return device.deviceType === 'real';
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
else if (deviceTypes.androidDeviceType === 'simulated') {
|
|
158
|
-
const simulated = devices.filter((device) => {
|
|
159
|
-
return device.deviceType === 'emulator';
|
|
160
|
-
});
|
|
161
|
-
if (this.pluginArgs.bootedEmulators) {
|
|
162
|
-
return simulated.filter((device) => device.state === 'device');
|
|
163
|
-
}
|
|
164
|
-
return simulated;
|
|
165
|
-
// return both real and simulated (emulated) devices
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
if (this.pluginArgs.bootedEmulators) {
|
|
169
|
-
return devices.filter((device) => {
|
|
170
|
-
if (device.deviceType === 'emulator') {
|
|
171
|
-
return device.state === 'device';
|
|
172
|
-
}
|
|
173
|
-
return true;
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
return devices;
|
|
177
|
-
}
|
|
168
|
+
return devices.filter((device) => includesAndroidDevice(device, deviceTypes.androidDeviceType, this.pluginArgs.bootedEmulators));
|
|
178
169
|
}
|
|
179
170
|
catch (e) {
|
|
180
171
|
logger_1.default.error(`Error while getting android devices. Error: ${e instanceof Error ? e.message : e}`);
|
|
@@ -196,7 +187,11 @@ let AndroidDeviceManager = class AndroidDeviceManager {
|
|
|
196
187
|
adbInstance.adbRemoteHost === null
|
|
197
188
|
? this.pluginArgs.bindHostOrIp
|
|
198
189
|
: adbInstance.adbRemoteHost;
|
|
199
|
-
|
|
190
|
+
// This server's own row for the phone: the exact host deviceInfo
|
|
191
|
+
// would write. A node on this machine shares the IP, so its row
|
|
192
|
+
// for the same phone must not be taken for ours.
|
|
193
|
+
const ownHost = (0, localDeviceHosts_1.androidDeviceHost)(this.pluginArgs, this.hostPort, adbInstance);
|
|
194
|
+
const existingDevice = existingDeviceDetails.find((dev) => dev.udid === device.udid && dev.host === ownHost);
|
|
200
195
|
if (existingDevice) {
|
|
201
196
|
if (device.state === 'device') {
|
|
202
197
|
logger_1.default.debug(`Android Device details for ${device.udid} already available and online`);
|
|
@@ -263,16 +258,7 @@ let AndroidDeviceManager = class AndroidDeviceManager {
|
|
|
263
258
|
}
|
|
264
259
|
// Optional: a device that can't say still registers, as before.
|
|
265
260
|
const identity = yield this.getIdentity(adbInstance, device.udid, realDevice);
|
|
266
|
-
|
|
267
|
-
if (adbInstance.adbHost != null) {
|
|
268
|
-
host = `http://${adbInstance.adbHost}:${adbInstance.adbPort}`;
|
|
269
|
-
}
|
|
270
|
-
else if (pluginArgs.remoteMachineProxyIP !== undefined) {
|
|
271
|
-
host = `http://${pluginArgs.remoteMachineProxyIP}:${hostPort}`;
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
host = `http://${pluginArgs.bindHostOrIp}:${hostPort}`;
|
|
275
|
-
}
|
|
261
|
+
const host = (0, localDeviceHosts_1.androidDeviceHost)(pluginArgs, hostPort, adbInstance);
|
|
276
262
|
return Object.assign({ adbRemoteHost: (_a = adbInstance.adbRemoteHost) !== null && _a !== void 0 ? _a : undefined, adbPort: adbInstance.adbPort, systemPort, sdk: sdk !== null && sdk !== void 0 ? sdk : 'unknown', realDevice, name: name !== null && name !== void 0 ? name : 'unknown', busy: false, state: device.state, udid: device.udid, platform: 'android', deviceType: realDevice ? 'real' : 'emulator', host, totalUtilizationTimeMilliSec: totalUtilizationTimeMilliSec, sessionStartTime: 0, userBlocked: false, ip: (0, networkAddresses_1.sanitizeDeviceNetworkIp)(yield this.getDeviceIp(adbInstance, device.udid)), cpuArchitecture: yield this.getCpuArchitecture(adbInstance, device.udid) }, identity);
|
|
277
263
|
});
|
|
278
264
|
}
|
|
@@ -477,9 +463,7 @@ let AndroidDeviceManager = class AndroidDeviceManager {
|
|
|
477
463
|
const adbRemote = pluginArgs.adbRemote;
|
|
478
464
|
if (adbRemote !== undefined && adbRemote.length > 0) {
|
|
479
465
|
const promises = adbRemote.map((value) => __awaiter(this, void 0, void 0, function* () {
|
|
480
|
-
const
|
|
481
|
-
const adbHost = adbRemoteValue[0];
|
|
482
|
-
const adbPort = parseInt(adbRemoteValue[1]) || 5037;
|
|
466
|
+
const { adbHost, adbPort } = (0, localDeviceHosts_1.parseAdbRemote)(value);
|
|
483
467
|
const cloneAdb = originalADB.clone({
|
|
484
468
|
remoteAdbHost: adbHost,
|
|
485
469
|
adbPort,
|
|
@@ -520,6 +504,16 @@ let AndroidDeviceManager = class AndroidDeviceManager {
|
|
|
520
504
|
return;
|
|
521
505
|
}
|
|
522
506
|
this.cancelAbort(newDevice.udid);
|
|
507
|
+
// Discovery's rule, before anything is leased or written: a phone of a
|
|
508
|
+
// kind this server doesn't serve was listed (and could be allocated)
|
|
509
|
+
// until the next sync pruned it. A phone that can't say goes on to
|
|
510
|
+
// deviceInfo, which needs the same property and skips it.
|
|
511
|
+
const realDevice = yield this.isRealDevice(originalADB, newDevice.udid).catch(() => undefined);
|
|
512
|
+
if (realDevice !== undefined &&
|
|
513
|
+
!includesAndroidDevice({ deviceType: realDevice ? 'real' : 'emulator', state: newDevice.state }, this.pluginArgs.androidDeviceType, this.pluginArgs.bootedEmulators)) {
|
|
514
|
+
logger_1.default.info(`Device ${newDevice.udid} is ${realDevice ? 'a real phone' : 'an emulator'}, and androidDeviceType is ${this.pluginArgs.androidDeviceType}. Ignoring`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
523
517
|
const trackedDevice = yield this.deviceInfo(newDevice, originalADB, this.pluginArgs, this.hostPort);
|
|
524
518
|
if (!trackedDevice) {
|
|
525
519
|
logger_1.default.info(`Cannot get device info for ${newDevice.udid}. Skipping`);
|
|
@@ -577,10 +571,14 @@ let AndroidDeviceManager = class AndroidDeviceManager {
|
|
|
577
571
|
}
|
|
578
572
|
onDeviceRemoved(device, pluginArgs) {
|
|
579
573
|
return __awaiter(this, void 0, void 0, function* () {
|
|
574
|
+
// The host onDeviceAdded files the phone under (deviceInfo with the local
|
|
575
|
+
// adb). Exact: a bare IP is matched as a substring by the store and by
|
|
576
|
+
// the hub, and would also take another server's row with this udid.
|
|
580
577
|
const clonedDevice = {
|
|
581
578
|
udid: device['id'],
|
|
582
|
-
host: pluginArgs.
|
|
579
|
+
host: (0, localDeviceHosts_1.androidDeviceHost)(pluginArgs, this.hostPort),
|
|
583
580
|
state: device.type,
|
|
581
|
+
nodeId: this.nodeId,
|
|
584
582
|
};
|
|
585
583
|
if (pluginArgs.hub != undefined) {
|
|
586
584
|
const nodeDevices = new NodeDevices_1.default(pluginArgs.hub, {
|
|
@@ -63,6 +63,9 @@ const schedule = __importStar(require("node-schedule"));
|
|
|
63
63
|
const web_config_service_1 = require("../data-service/web-config-service");
|
|
64
64
|
const SessionManager_1 = require("../sessions/SessionManager");
|
|
65
65
|
const EventBus_1 = require("../services/EventBus");
|
|
66
|
+
const deviceClaims_1 = require("../data-service/deviceClaims");
|
|
67
|
+
const PluginContext_1 = require("../PluginContext");
|
|
68
|
+
const localDeviceHosts_1 = require("./localDeviceHosts");
|
|
66
69
|
let HealthMonitorService = class HealthMonitorService {
|
|
67
70
|
constructor(webConfigService) {
|
|
68
71
|
this.webConfigService = webConfigService;
|
|
@@ -145,14 +148,26 @@ let HealthMonitorService = class HealthMonitorService {
|
|
|
145
148
|
const devices = yield store.getAllDevices();
|
|
146
149
|
const manager = typedi_1.Container.get(index_1.XenonManager);
|
|
147
150
|
const instances = yield manager.deviceInstances();
|
|
151
|
+
const context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
152
|
+
const local = (0, localDeviceHosts_1.localDeviceHosts)(context.pluginArgs, context.port);
|
|
148
153
|
for (const device of devices) {
|
|
149
|
-
// Only
|
|
150
|
-
|
|
154
|
+
// Only this server's own phones. A hub keeps its nodes' phones in the
|
|
155
|
+
// same table; its adb can't reach them, so each came back unhealthy,
|
|
156
|
+
// was written over the node's report, "recovered" and, when busy with
|
|
157
|
+
// a session not in this hub's memory, reclaimed. Their node checks them.
|
|
158
|
+
if (device.cloud || !(0, localDeviceHosts_1.isOwnDevice)(local, context.nodeId, device))
|
|
151
159
|
continue;
|
|
152
160
|
// Principal Intelligence: Health monitor must detect "Zombie Busy" devices.
|
|
153
161
|
// A device is a Zombie if it's marked busy in DB but NOT found in Hub's session map
|
|
154
162
|
// AND not currently streaming to a manual viewer.
|
|
155
163
|
if (device.busy) {
|
|
164
|
+
// Not a zombie: a session still being created on it (its session is
|
|
165
|
+
// in no map yet; the claim has its own timeout), or, on a hub, a
|
|
166
|
+
// node reporting it busy there (deviceClaims.ts).
|
|
167
|
+
if (((0, deviceClaims_1.isPendingClaim)(device) && !(0, deviceClaims_1.pendingClaimExpired)(device, Date.now())) ||
|
|
168
|
+
device.nodeBusy) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
156
171
|
const hasActiveSession = SessionManager_1.SESSION_MANAGER.isValidSession(device.session_id || '');
|
|
157
172
|
const isManualStream = (_a = device.session_id) === null || _a === void 0 ? void 0 : _a.startsWith('manual_');
|
|
158
173
|
// NEW: Check for active manual streams (dashboard viewers)
|
|
@@ -99,7 +99,11 @@ let IOSDeviceManager = class IOSDeviceManager {
|
|
|
99
99
|
result.screenHeight = String(streamStatus.screenHeight);
|
|
100
100
|
}
|
|
101
101
|
else {
|
|
102
|
-
|
|
102
|
+
// This phone's own row: another server's for the same udid is not it.
|
|
103
|
+
const storeDevice = yield device_store_1.DeviceStoreFactory.getStore().findDevice({
|
|
104
|
+
udid: device.udid,
|
|
105
|
+
host: device.host,
|
|
106
|
+
});
|
|
103
107
|
if (storeDevice === null || storeDevice === void 0 ? void 0 : storeDevice.screenWidth) {
|
|
104
108
|
result.screenWidth = storeDevice.screenWidth;
|
|
105
109
|
result.screenHeight = storeDevice.screenHeight;
|
|
@@ -67,13 +67,18 @@ class NodeDevices {
|
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Ask the hub to forget this node's phones filed under `host`. With the
|
|
72
|
+
* node's id the hub takes every phone this node reported, whatever its
|
|
73
|
+
* host; it never takes another server's (grid.ts, removeNodeDevices).
|
|
74
|
+
*/
|
|
75
|
+
unRegisterNode(host, nodeId) {
|
|
71
76
|
return __awaiter(this, void 0, void 0, function* () {
|
|
72
77
|
logger_1.default.info(`Unregistering node ${this.host}/xenon/api/register`);
|
|
73
78
|
try {
|
|
74
79
|
const client = InternalHttpClient_1.InternalHttpClient.getClient(this.tlsRejectUnauthorized);
|
|
75
80
|
yield client.post(`${this.host}/xenon/api/register`, [], {
|
|
76
|
-
params: { type: 'unregister', host },
|
|
81
|
+
params: Object.assign({ type: 'unregister', host }, (nodeId ? { nodeId } : {})),
|
|
77
82
|
headers: this.nodeHeaders(),
|
|
78
83
|
});
|
|
79
84
|
logger_1.default.info(`Unregistered node ${host} from hub`);
|
|
@@ -57,7 +57,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
57
57
|
const child_process_1 = require("child_process");
|
|
58
58
|
const typedi_1 = require("typedi");
|
|
59
59
|
const logger_1 = __importDefault(require("../../logger"));
|
|
60
|
-
const
|
|
60
|
+
const ownDeviceRow_1 = require("../ownDeviceRow");
|
|
61
61
|
const H264Multiplexer_1 = require("./H264Multiplexer");
|
|
62
62
|
const h264NalParser_1 = require("./h264NalParser");
|
|
63
63
|
const androidH264Config_1 = require("../../app/routers/androidH264Config");
|
|
@@ -91,6 +91,8 @@ let AndroidH264StreamService = class AndroidH264StreamService {
|
|
|
91
91
|
this.startWatchdog();
|
|
92
92
|
}
|
|
93
93
|
startWatchdog() {
|
|
94
|
+
// unref()ed below: the server's listener keeps the process alive; this only
|
|
95
|
+
// needs to fire while it runs.
|
|
94
96
|
setInterval(() => {
|
|
95
97
|
const now = Date.now();
|
|
96
98
|
for (const [udid, s] of this.sessions.entries()) {
|
|
@@ -111,7 +113,7 @@ let AndroidH264StreamService = class AndroidH264StreamService {
|
|
|
111
113
|
void this.stop(udid).then(() => (0, previewHold_1.releaseIdlePreviewHold)(udid));
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
|
-
}, 60000);
|
|
116
|
+
}, 60000).unref();
|
|
115
117
|
}
|
|
116
118
|
getMultiplexer(udid) {
|
|
117
119
|
var _a;
|
|
@@ -150,7 +152,13 @@ let AndroidH264StreamService = class AndroidH264StreamService {
|
|
|
150
152
|
session.capture = yield this.openCapture(udid, onPacket, source);
|
|
151
153
|
// Give the first keyframe/config a moment so callers get a ready stream,
|
|
152
154
|
// but never block start-up indefinitely.
|
|
153
|
-
|
|
155
|
+
let cap;
|
|
156
|
+
try {
|
|
157
|
+
yield Promise.race([firstConfig, new Promise((r) => (cap = setTimeout(r, 3000)))]);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
clearTimeout(cap);
|
|
161
|
+
}
|
|
154
162
|
return mux;
|
|
155
163
|
}
|
|
156
164
|
catch (e) {
|
|
@@ -266,7 +274,7 @@ let AndroidH264StreamService = class AndroidH264StreamService {
|
|
|
266
274
|
startScrcpyOrThrow(udid, onPacket) {
|
|
267
275
|
return __awaiter(this, void 0, void 0, function* () {
|
|
268
276
|
const { ScrcpyServerSession, scrcpyMaxSizeFromDims } = yield Promise.resolve().then(() => __importStar(require('./ScrcpyServerSession')));
|
|
269
|
-
const device = yield
|
|
277
|
+
const device = yield (0, ownDeviceRow_1.findOwnDevice)(udid);
|
|
270
278
|
const maxSize = scrcpyMaxSizeFromDims(Number(device === null || device === void 0 ? void 0 : device.screenWidth), Number(device === null || device === void 0 ? void 0 : device.screenHeight));
|
|
271
279
|
const parser = new h264NalParser_1.H264NalParser();
|
|
272
280
|
const session = new ScrcpyServerSession(udid);
|
|
@@ -405,7 +413,7 @@ let AndroidH264StreamService = class AndroidH264StreamService {
|
|
|
405
413
|
resolveCaptureSize(udid) {
|
|
406
414
|
return __awaiter(this, void 0, void 0, function* () {
|
|
407
415
|
try {
|
|
408
|
-
const device = yield
|
|
416
|
+
const device = yield (0, ownDeviceRow_1.findOwnDevice)(udid);
|
|
409
417
|
const sw = Number(device === null || device === void 0 ? void 0 : device.screenWidth);
|
|
410
418
|
const sh = Number(device === null || device === void 0 ? void 0 : device.screenHeight);
|
|
411
419
|
if (Number.isFinite(sw) && Number.isFinite(sh) && sw > 0 && sh > 0) {
|
|
@@ -59,6 +59,7 @@ const http_1 = __importDefault(require("http"));
|
|
|
59
59
|
const sharp_1 = __importDefault(require("sharp"));
|
|
60
60
|
const logger_1 = __importDefault(require("../../logger"));
|
|
61
61
|
const device_store_1 = require("../../data-service/device-store");
|
|
62
|
+
const ownDeviceRow_1 = require("../ownDeviceRow");
|
|
62
63
|
const DeviceLockManager_1 = require("./DeviceLockManager");
|
|
63
64
|
const typedi_1 = require("typedi");
|
|
64
65
|
const ResourceIsolationService_1 = require("../../services/ResourceIsolationService");
|
|
@@ -80,6 +81,8 @@ let AndroidStreamService = class AndroidStreamService {
|
|
|
80
81
|
this.startWatchdog();
|
|
81
82
|
}
|
|
82
83
|
startWatchdog() {
|
|
84
|
+
// unref()ed below: the server's listener keeps the process alive; this only
|
|
85
|
+
// needs to fire while it runs.
|
|
83
86
|
setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
84
87
|
const now = Date.now();
|
|
85
88
|
for (const [udid, session] of this.sessions.entries()) {
|
|
@@ -92,7 +95,7 @@ let AndroidStreamService = class AndroidStreamService {
|
|
|
92
95
|
}
|
|
93
96
|
// Every minute, not every hour: an hourly check left an abandoned
|
|
94
97
|
// preview hold in place for up to 70 minutes. The loop is in memory.
|
|
95
|
-
}), 60000);
|
|
98
|
+
}), 60000).unref();
|
|
96
99
|
}
|
|
97
100
|
/**
|
|
98
101
|
* Resolve the actual adb binary path (and remote-host args) via the same
|
|
@@ -272,7 +275,7 @@ let AndroidStreamService = class AndroidStreamService {
|
|
|
272
275
|
`(${decision.reason}); discarding it and starting fresh.`);
|
|
273
276
|
yield this.disposeSession(udid, existing);
|
|
274
277
|
}
|
|
275
|
-
const device = yield
|
|
278
|
+
const device = yield (0, ownDeviceRow_1.findOwnDevice)(udid);
|
|
276
279
|
if (!device)
|
|
277
280
|
throw new Error(`Device ${udid} not found in DB`);
|
|
278
281
|
// Resolve the real adb binary once, up front, so the capture loop never
|
|
@@ -315,7 +318,7 @@ let AndroidStreamService = class AndroidStreamService {
|
|
|
315
318
|
catch (_d) {
|
|
316
319
|
/* ignore */
|
|
317
320
|
}
|
|
318
|
-
yield portAllocator.release(mjpegPort).catch(() => undefined);
|
|
321
|
+
yield portAllocator.release(mjpegPort, udid).catch(() => undefined);
|
|
319
322
|
yield portAllocator.blockPort(mjpegPort, 'mjpeg').catch(() => undefined);
|
|
320
323
|
}
|
|
321
324
|
}
|
|
@@ -346,7 +349,7 @@ let AndroidStreamService = class AndroidStreamService {
|
|
|
346
349
|
logger_1.default.info(`[${udid}] MJPEG stream primed with first frame.`);
|
|
347
350
|
}
|
|
348
351
|
// Update device info in store to ensure other services (like VideoPipeline) can find the port
|
|
349
|
-
const updatedDevice = yield
|
|
352
|
+
const updatedDevice = yield (0, ownDeviceRow_1.findOwnDevice)(udid);
|
|
350
353
|
if (updatedDevice) {
|
|
351
354
|
yield device_store_1.DeviceStoreFactory.getStore().updateDevice(udid, updatedDevice.host, {
|
|
352
355
|
mjpegServerPort: mjpegPort,
|
|
@@ -393,7 +396,7 @@ let AndroidStreamService = class AndroidStreamService {
|
|
|
393
396
|
if (this.sessions.get(udid) === session)
|
|
394
397
|
this.sessions.delete(udid);
|
|
395
398
|
try {
|
|
396
|
-
yield typedi_1.Container.get(PortAllocator_1.PortAllocator).release(session.mjpegPort);
|
|
399
|
+
yield typedi_1.Container.get(PortAllocator_1.PortAllocator).release(session.mjpegPort, udid);
|
|
397
400
|
}
|
|
398
401
|
catch (e) {
|
|
399
402
|
logger_1.default.warn(`[${udid}] Failed to release mjpeg port lease while restarting stream: ${e}`);
|
|
@@ -420,7 +423,7 @@ let AndroidStreamService = class AndroidStreamService {
|
|
|
420
423
|
// 'mjpeg' lease is released here — the device-level 'system' port (leased
|
|
421
424
|
// by AndroidDeviceManager) belongs to the device lifecycle, not the stream.
|
|
422
425
|
try {
|
|
423
|
-
yield typedi_1.Container.get(PortAllocator_1.PortAllocator).release(session.mjpegPort);
|
|
426
|
+
yield typedi_1.Container.get(PortAllocator_1.PortAllocator).release(session.mjpegPort, udid);
|
|
424
427
|
}
|
|
425
428
|
catch (e) {
|
|
426
429
|
logger_1.default.warn(`[${udid}] Failed to release mjpeg port lease on stop: ${e}`);
|
|
@@ -16,7 +16,7 @@ exports.mayReleasePreviewHold = mayReleasePreviewHold;
|
|
|
16
16
|
exports.releaseIdlePreviewHold = releaseIdlePreviewHold;
|
|
17
17
|
const typedi_1 = require("typedi");
|
|
18
18
|
const logger_1 = __importDefault(require("../../logger"));
|
|
19
|
-
const
|
|
19
|
+
const ownDeviceRow_1 = require("../ownDeviceRow");
|
|
20
20
|
const device_service_1 = require("../../data-service/device-service");
|
|
21
21
|
const manualLock_1 = require("../../services/recording/manualLock");
|
|
22
22
|
const recording_store_1 = require("../../services/recording/recording-store");
|
|
@@ -43,7 +43,7 @@ function releaseIdlePreviewHold(udid) {
|
|
|
43
43
|
return __awaiter(this, void 0, void 0, function* () {
|
|
44
44
|
var _a, _b, _c, _d, _e;
|
|
45
45
|
try {
|
|
46
|
-
const device = yield
|
|
46
|
+
const device = yield (0, ownDeviceRow_1.findOwnDevice)(udid);
|
|
47
47
|
if (!device)
|
|
48
48
|
return false;
|
|
49
49
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|