@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
|
@@ -13,7 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.removeDevice = removeDevice;
|
|
16
|
-
exports.
|
|
16
|
+
exports.removeNodeDevices = removeNodeDevices;
|
|
17
17
|
exports.addNewDevice = addNewDevice;
|
|
18
18
|
exports.syncDiscoveredDevices = syncDiscoveredDevices;
|
|
19
19
|
exports.setSimulatorState = setSimulatorState;
|
|
@@ -23,6 +23,7 @@ exports.filterRowsByVisibleDevice = filterRowsByVisibleDevice;
|
|
|
23
23
|
exports.getDevices = getDevices;
|
|
24
24
|
exports.getDevice = getDevice;
|
|
25
25
|
exports.updatedAllocatedDevice = updatedAllocatedDevice;
|
|
26
|
+
exports.claimDeviceForSession = claimDeviceForSession;
|
|
26
27
|
exports.updateDeviceProgress = updateDeviceProgress;
|
|
27
28
|
exports.updateCmdExecutedTime = updateCmdExecutedTime;
|
|
28
29
|
exports.userBlockDevice = userBlockDevice;
|
|
@@ -30,6 +31,9 @@ exports.userUnblockDevice = userUnblockDevice;
|
|
|
30
31
|
exports.blockDevice = blockDevice;
|
|
31
32
|
exports.unblockDevice = unblockDevice;
|
|
32
33
|
exports.unblockDeviceMatchingFilter = unblockDeviceMatchingFilter;
|
|
34
|
+
exports.releaseSessionDevices = releaseSessionDevices;
|
|
35
|
+
exports.releaseSessionDevice = releaseSessionDevice;
|
|
36
|
+
exports.releasePendingClaim = releasePendingClaim;
|
|
33
37
|
exports.reserveDevice = reserveDevice;
|
|
34
38
|
exports.releaseReservation = releaseReservation;
|
|
35
39
|
exports.isDeviceReserved = isDeviceReserved;
|
|
@@ -43,6 +47,7 @@ const typedi_1 = require("typedi");
|
|
|
43
47
|
const CircuitBreaker_1 = require("./CircuitBreaker");
|
|
44
48
|
const NotificationService_1 = require("../services/NotificationService");
|
|
45
49
|
const deviceFieldOwners_1 = require("./deviceFieldOwners");
|
|
50
|
+
const deviceClaims_1 = require("./deviceClaims");
|
|
46
51
|
const SocketServer_1 = require("../services/SocketServer");
|
|
47
52
|
const prisma_1 = require("../prisma");
|
|
48
53
|
const manualLock_1 = require("../services/recording/manualLock");
|
|
@@ -63,8 +68,8 @@ const store = new Proxy({}, {
|
|
|
63
68
|
function deviceScope(udid, teamId) {
|
|
64
69
|
return teamId === undefined ? { udid } : { udid, teamId };
|
|
65
70
|
}
|
|
66
|
-
function removeDevice(
|
|
67
|
-
return __awaiter(this,
|
|
71
|
+
function removeDevice(devices_1) {
|
|
72
|
+
return __awaiter(this, arguments, void 0, function* (devices, options = {}) {
|
|
68
73
|
for (const device of devices) {
|
|
69
74
|
logger_1.default.info(`Removing device ${device.udid} from host ${device.host}`);
|
|
70
75
|
const socket = typedi_1.Container.get(SocketServer_1.SocketServer);
|
|
@@ -75,18 +80,44 @@ function removeDevice(devices) {
|
|
|
75
80
|
const team = socket.hasScopedDashboard()
|
|
76
81
|
? yield typedi_1.Container.get(DeviceTeamResolver_1.DeviceTeamResolver).resolve(device.udid)
|
|
77
82
|
: undefined;
|
|
78
|
-
yield store.removeDevices({ udid: device.udid, host: device.host });
|
|
83
|
+
yield store.removeDevices({ udid: device.udid, host: device.host }, options);
|
|
79
84
|
typedi_1.Container.get(NotificationService_1.NotificationService).dispatchEvent('device_offline', device);
|
|
80
85
|
void socket.emitToDashboardForDevices('device_removed', device, deviceScope(device.udid, (team === null || team === void 0 ? void 0 : team.known) ? team.teamId : undefined));
|
|
81
86
|
}
|
|
82
87
|
});
|
|
83
88
|
}
|
|
84
|
-
|
|
89
|
+
/**
|
|
90
|
+
* The phones a node asks the hub to forget (`POST /register`, `type=remove`
|
|
91
|
+
* for one phone, `type=unregister` when it shuts down). Only that node's: the
|
|
92
|
+
* rows its `nodeId` names when it sends one, else those filed under exactly
|
|
93
|
+
* its `host`, and never one of this server's own (`isOwn`). A udid-less
|
|
94
|
+
* `remove` names no phone and takes nothing.
|
|
95
|
+
*
|
|
96
|
+
* The store matched a host that isn't a URL as a substring, and a remove with
|
|
97
|
+
* no host by udid alone, so a node (an older one sends a bare IP) could delete
|
|
98
|
+
* the hub's own phones, or another node's. Returns how many rows went.
|
|
99
|
+
*/
|
|
100
|
+
function removeNodeDevices(removals, isOwn) {
|
|
85
101
|
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
const all = yield store.getAllDevices();
|
|
103
|
+
const gone = new Map();
|
|
104
|
+
for (const removal of removals) {
|
|
105
|
+
if (!removal.nodeId && !removal.host)
|
|
106
|
+
continue;
|
|
107
|
+
for (const device of all) {
|
|
108
|
+
if (isOwn(device))
|
|
109
|
+
continue;
|
|
110
|
+
if (removal.udid !== undefined && device.udid !== removal.udid)
|
|
111
|
+
continue;
|
|
112
|
+
const theirs = removal.nodeId
|
|
113
|
+
? device.nodeId === removal.nodeId
|
|
114
|
+
: device.host === removal.host;
|
|
115
|
+
if (theirs)
|
|
116
|
+
gone.set(`${device.udid}\u0000${device.host}`, device);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
yield removeDevice([...gone.values()].map((d) => ({ udid: d.udid, host: d.host })), { exactHost: true });
|
|
120
|
+
return gone.size;
|
|
90
121
|
});
|
|
91
122
|
}
|
|
92
123
|
function addNewDevice(devices_1, host_1) {
|
|
@@ -237,6 +268,22 @@ function updatedAllocatedDevice(device, updateData) {
|
|
|
237
268
|
yield store.updateDevice(device.udid, device.host, updateData);
|
|
238
269
|
});
|
|
239
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Put a created session on the claim its phone was allocated with
|
|
273
|
+
* (`device.claimedAt`), writing `updateData` with it. False, and nothing
|
|
274
|
+
* written, when another session holds the phone now: its claim is not
|
|
275
|
+
* overwritten.
|
|
276
|
+
*/
|
|
277
|
+
function claimDeviceForSession(device, sessionId, updateData) {
|
|
278
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
279
|
+
const claimed = yield store.claimForSession(device.udid, device.host, device.claimedAt, sessionId, updateData);
|
|
280
|
+
if (!claimed) {
|
|
281
|
+
logger_1.default.error(`Session ${sessionId} was created on ${device.udid} at ${device.host}, but another ` +
|
|
282
|
+
'session has claimed the device since. Leaving that claim in place.');
|
|
283
|
+
}
|
|
284
|
+
return claimed;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
240
287
|
function updateDeviceProgress(udid_1, host_1, progress_1) {
|
|
241
288
|
return __awaiter(this, arguments, void 0, function* (udid, host, progress, extra = {}) {
|
|
242
289
|
logger_1.default.debug(`[${udid}] progress: ${progress}`);
|
|
@@ -249,9 +296,9 @@ function updateDeviceProgress(udid_1, host_1, progress_1) {
|
|
|
249
296
|
}
|
|
250
297
|
function updateCmdExecutedTime(sessionId) {
|
|
251
298
|
return __awaiter(this, void 0, void 0, function* () {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
299
|
+
// One conditional write. Reading the row and writing all of it back would
|
|
300
|
+
// put back a claim released, or a node's report taken, in between.
|
|
301
|
+
yield store.touchSession(sessionId, new Date().getTime());
|
|
255
302
|
});
|
|
256
303
|
}
|
|
257
304
|
/**
|
|
@@ -297,53 +344,109 @@ function blockDevice(udid, host, sessionId) {
|
|
|
297
344
|
}, { udid });
|
|
298
345
|
});
|
|
299
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Free a phone whatever holds it: a manual hold (a preview, a recording) or
|
|
349
|
+
* an admin's release. A session's phone is released with
|
|
350
|
+
* releaseSessionDevices / releaseSessionDevice / releasePendingClaim instead,
|
|
351
|
+
* keyed on its claim.
|
|
352
|
+
*
|
|
353
|
+
* With a host, only that row. A hub also stores its nodes' phones, and a node
|
|
354
|
+
* on the same machine lists the same udid under its own host: matching the
|
|
355
|
+
* udid alone freed that row too, with a session on it.
|
|
356
|
+
*/
|
|
300
357
|
function unblockDevice(udid, host) {
|
|
301
358
|
return __awaiter(this, void 0, void 0, function* () {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
359
|
+
const devices = yield store.findDevices(host === undefined ? { udid } : { udid, host });
|
|
360
|
+
yield forceRelease(devices);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/** unblockDevice for every phone matching `filter` (getDevices' filter). */
|
|
364
|
+
function unblockDeviceMatchingFilter(filter) {
|
|
365
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
366
|
+
yield forceRelease(yield store.getDevices(filter));
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
function forceRelease(devices) {
|
|
370
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
371
|
+
yield Promise.all(devices.map((device) => __awaiter(this, void 0, void 0, function* () {
|
|
372
|
+
const totalUtilization = utilizationAfter(device);
|
|
373
|
+
yield (0, device_utils_1.setUtilizationTime)(device.udid, totalUtilization);
|
|
374
|
+
yield store.updateDevice(device.udid, device.host, Object.assign(Object.assign({}, deviceClaims_1.CLAIM_RESET), { busy: false, totalUtilizationTimeMilliSec: totalUtilization }));
|
|
375
|
+
logger_1.default.debug(`Unblocked device ${device.udid}`);
|
|
376
|
+
emitUnblocked(device);
|
|
377
|
+
}))).catch((error) => {
|
|
378
|
+
logger_1.default.error(`Unable to unblock device: ${error}`);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* End a session's claim on its phone, keyed on the session's id: a release
|
|
384
|
+
* for a session that has ended, arriving after its phone went to another one,
|
|
385
|
+
* matches nothing. The phone stays busy while its node still reports it busy
|
|
386
|
+
* (deviceClaims.ts). Never throws.
|
|
387
|
+
*/
|
|
388
|
+
function releaseSessionDevices(sessionId) {
|
|
389
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
390
|
+
try {
|
|
391
|
+
const rows = new Map();
|
|
392
|
+
for (const device of [
|
|
393
|
+
...(yield store.findDevices({ claimSessionId: sessionId })),
|
|
394
|
+
...(yield store.findDevices({ session_id: sessionId })),
|
|
395
|
+
]) {
|
|
396
|
+
rows.set(`${device.udid}@${device.host}`, device);
|
|
397
|
+
}
|
|
398
|
+
yield Promise.all([...rows.values()].map((device) => releaseClaimOn(device, { sessionId })));
|
|
309
399
|
}
|
|
310
|
-
|
|
311
|
-
|
|
400
|
+
catch (error) {
|
|
401
|
+
logger_1.default.error(`Unable to release the device of session ${sessionId}: ${error}`);
|
|
312
402
|
}
|
|
313
403
|
});
|
|
314
404
|
}
|
|
315
|
-
|
|
405
|
+
/** releaseSessionDevices for one phone. */
|
|
406
|
+
function releaseSessionDevice(udid, host, sessionId) {
|
|
316
407
|
return __awaiter(this, void 0, void 0, function* () {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
408
|
+
const device = yield store.findDevice({ udid, host });
|
|
409
|
+
return device ? releaseClaimOn(device, { sessionId }) : false;
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Give back a phone allocated for a session that was never created: the
|
|
414
|
+
* pending claim it was allocated with (`claimedAt`, as the allocation read
|
|
415
|
+
* it). If that claim has gone (timed out, and the phone taken since) nothing
|
|
416
|
+
* is written.
|
|
417
|
+
*/
|
|
418
|
+
function releasePendingClaim(allocated) {
|
|
419
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
420
|
+
var _a;
|
|
421
|
+
const device = yield store.findDevice({ udid: allocated.udid, host: allocated.host });
|
|
422
|
+
return device ? releaseClaimOn(device, { claimedAt: (_a = allocated.claimedAt) !== null && _a !== void 0 ? _a : null }) : false;
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
function releaseClaimOn(device, ref) {
|
|
426
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
427
|
+
const totalUtilization = utilizationAfter(device);
|
|
428
|
+
const released = yield store.releaseClaim(device.udid, device.host, ref, {
|
|
429
|
+
totalUtilizationTimeMilliSec: totalUtilization,
|
|
430
|
+
});
|
|
431
|
+
if (!released) {
|
|
432
|
+
logger_1.default.info(`Not releasing ${device.udid} at ${device.host}: it no longer holds ${JSON.stringify(ref)}`);
|
|
433
|
+
return false;
|
|
344
434
|
}
|
|
435
|
+
yield (0, device_utils_1.setUtilizationTime)(device.udid, totalUtilization);
|
|
436
|
+
logger_1.default.debug(`Released ${JSON.stringify(ref)} on ${device.udid}`);
|
|
437
|
+
emitUnblocked(device);
|
|
438
|
+
return true;
|
|
345
439
|
});
|
|
346
440
|
}
|
|
441
|
+
/** The phone's total use once the session on it now ends. */
|
|
442
|
+
function utilizationAfter(device) {
|
|
443
|
+
var _a;
|
|
444
|
+
const started = device.sessionStartTime;
|
|
445
|
+
return ((_a = device.totalUtilizationTimeMilliSec) !== null && _a !== void 0 ? _a : 0) + (started ? Date.now() - started : 0);
|
|
446
|
+
}
|
|
447
|
+
function emitUnblocked(device) {
|
|
448
|
+
void typedi_1.Container.get(SocketServer_1.SocketServer).emitToDashboardForDevices('device_unblocked', { udid: device.udid, host: device.host }, deviceScope(device.udid, device.teamId));
|
|
449
|
+
}
|
|
347
450
|
/**
|
|
348
451
|
* Reserve a device for exclusive manual use
|
|
349
452
|
* @param udid Device UDID
|
|
@@ -13,6 +13,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.DeviceStoreFactory = void 0;
|
|
16
|
+
const deviceFieldOwners_1 = require("./deviceFieldOwners");
|
|
17
|
+
const deviceClaims_1 = require("./deviceClaims");
|
|
16
18
|
const logger_1 = __importDefault(require("../logger"));
|
|
17
19
|
const semver_1 = __importDefault(require("semver"));
|
|
18
20
|
const db_1 = require("./db");
|
|
@@ -153,19 +155,37 @@ class LokiDeviceStore {
|
|
|
153
155
|
});
|
|
154
156
|
});
|
|
155
157
|
}
|
|
156
|
-
|
|
158
|
+
touchSession(sessionId, at) {
|
|
157
159
|
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
-
(yield db_1.XenonDatabase.DeviceModel)
|
|
160
|
+
(yield db_1.XenonDatabase.DeviceModel)
|
|
161
|
+
.chain()
|
|
162
|
+
.find({ session_id: sessionId })
|
|
163
|
+
.update((device) => {
|
|
164
|
+
device.lastCmdExecutedAt = at;
|
|
165
|
+
});
|
|
159
166
|
});
|
|
160
167
|
}
|
|
161
|
-
addDevices(
|
|
162
|
-
return __awaiter(this,
|
|
168
|
+
addDevices(devices_1) {
|
|
169
|
+
return __awaiter(this, arguments, void 0, function* (devices, options = {}) {
|
|
163
170
|
const deviceModel = yield db_1.XenonDatabase.DeviceModel;
|
|
164
171
|
const added = [];
|
|
165
172
|
for (const device of devices) {
|
|
166
173
|
const existing = deviceModel.findOne({ udid: device.udid, host: device.host });
|
|
174
|
+
// A node's report (PrismaDeviceStore.addNodeReport has the rule).
|
|
175
|
+
const nodeBusy = device.busy === true;
|
|
176
|
+
if (existing && options.nodeReport) {
|
|
177
|
+
Object.assign(existing, (0, deviceFieldOwners_1.pickNodeReportFields)(device), { nodeBusy });
|
|
178
|
+
if (nodeBusy)
|
|
179
|
+
existing.busy = true;
|
|
180
|
+
else if ((0, deviceClaims_1.isUnheld)(existing))
|
|
181
|
+
existing.busy = false;
|
|
182
|
+
deviceModel.update(existing);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
167
185
|
if (!existing) {
|
|
168
|
-
const cleanDevice =
|
|
186
|
+
const cleanDevice = options.nodeReport
|
|
187
|
+
? Object.assign(Object.assign({}, (0, deviceFieldOwners_1.pickNodeReportFields)(device)), { udid: device.udid, host: device.host, busy: nodeBusy, nodeBusy })
|
|
188
|
+
: Object.assign({}, device);
|
|
169
189
|
if (cleanDevice.host === undefined)
|
|
170
190
|
cleanDevice.host = 'Local';
|
|
171
191
|
if (cleanDevice.userBlocked === undefined)
|
|
@@ -185,14 +205,24 @@ class LokiDeviceStore {
|
|
|
185
205
|
return added;
|
|
186
206
|
});
|
|
187
207
|
}
|
|
208
|
+
// Loki's find() matches a host exactly already, so `exactHost` changes nothing here.
|
|
188
209
|
removeDevices(filter) {
|
|
189
210
|
return __awaiter(this, void 0, void 0, function* () {
|
|
190
211
|
(yield db_1.XenonDatabase.DeviceModel).chain().find(filter).remove();
|
|
191
212
|
});
|
|
192
213
|
}
|
|
193
|
-
clearStorage() {
|
|
214
|
+
clearStorage(onlyHosts) {
|
|
194
215
|
return __awaiter(this, void 0, void 0, function* () {
|
|
195
|
-
|
|
216
|
+
const model = yield db_1.XenonDatabase.DeviceModel;
|
|
217
|
+
if (onlyHosts) {
|
|
218
|
+
model
|
|
219
|
+
.chain()
|
|
220
|
+
.find({ host: { $in: [...onlyHosts] } })
|
|
221
|
+
.remove();
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
model.removeDataOnly();
|
|
225
|
+
}
|
|
196
226
|
});
|
|
197
227
|
}
|
|
198
228
|
findDevice(filter) {
|
|
@@ -205,8 +235,8 @@ class LokiDeviceStore {
|
|
|
205
235
|
return (yield db_1.XenonDatabase.DeviceModel).find(filter);
|
|
206
236
|
});
|
|
207
237
|
}
|
|
208
|
-
findAndLockDevice(
|
|
209
|
-
return __awaiter(this,
|
|
238
|
+
findAndLockDevice(filterOptions_1) {
|
|
239
|
+
return __awaiter(this, arguments, void 0, function* (filterOptions, options = {}) {
|
|
210
240
|
// Phase 2: exclude devices held by an active lease.
|
|
211
241
|
const activeLeases = yield prisma_1.prisma.lease.findMany({
|
|
212
242
|
where: { status: 'active' },
|
|
@@ -216,12 +246,64 @@ class LokiDeviceStore {
|
|
|
216
246
|
const devices = yield this.getDevices(filterOptions);
|
|
217
247
|
const available = devices.find((d) => !d.busy && !d.userBlocked && !blockedKeys.has(`${d.udid}@${d.host}`));
|
|
218
248
|
if (available) {
|
|
219
|
-
yield this.updateDevice(available.udid, available.host, { busy: true });
|
|
249
|
+
yield this.updateDevice(available.udid, available.host, Object.assign({ busy: true }, (options.claim ? { claimedAt: Date.now(), claimSessionId: null } : {})));
|
|
220
250
|
return available;
|
|
221
251
|
}
|
|
222
252
|
return null;
|
|
223
253
|
});
|
|
224
254
|
}
|
|
255
|
+
claimForSession(udid, host, claimedAt, sessionId, update) {
|
|
256
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
257
|
+
const model = yield db_1.XenonDatabase.DeviceModel;
|
|
258
|
+
const device = model.findOne({ udid, host });
|
|
259
|
+
if (!device || device.claimSessionId != null)
|
|
260
|
+
return false;
|
|
261
|
+
if (device.claimedAt != null && device.claimedAt !== claimedAt)
|
|
262
|
+
return false;
|
|
263
|
+
Object.assign(device, update, {
|
|
264
|
+
busy: true,
|
|
265
|
+
claimSessionId: sessionId,
|
|
266
|
+
claimedAt: claimedAt !== null && claimedAt !== void 0 ? claimedAt : Date.now(),
|
|
267
|
+
});
|
|
268
|
+
model.update(device);
|
|
269
|
+
return true;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
releaseClaim(udid, host, ref, update) {
|
|
273
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
274
|
+
const model = yield db_1.XenonDatabase.DeviceModel;
|
|
275
|
+
const device = model.findOne({ udid, host });
|
|
276
|
+
if (!device || !(0, deviceClaims_1.holdsClaim)(device, ref))
|
|
277
|
+
return false;
|
|
278
|
+
Object.assign(device, update, deviceClaims_1.CLAIM_RESET);
|
|
279
|
+
if ((0, deviceClaims_1.isUnheld)(device))
|
|
280
|
+
device.busy = false;
|
|
281
|
+
model.update(device);
|
|
282
|
+
return true;
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
releaseLeaseLock(udid, host) {
|
|
286
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
287
|
+
const model = yield db_1.XenonDatabase.DeviceModel;
|
|
288
|
+
const device = model.findOne({ udid, host });
|
|
289
|
+
if (!(device === null || device === void 0 ? void 0 : device.busy) || !(0, deviceClaims_1.isUnheld)(device))
|
|
290
|
+
return false;
|
|
291
|
+
device.busy = false;
|
|
292
|
+
model.update(device);
|
|
293
|
+
return true;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
markNodeBusy(udid, host) {
|
|
297
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
298
|
+
const model = yield db_1.XenonDatabase.DeviceModel;
|
|
299
|
+
const device = model.findOne({ udid, host });
|
|
300
|
+
if (!device)
|
|
301
|
+
return;
|
|
302
|
+
device.nodeBusy = true;
|
|
303
|
+
device.busy = true;
|
|
304
|
+
model.update(device);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
225
307
|
resetMetrics() {
|
|
226
308
|
return __awaiter(this, void 0, void 0, function* () {
|
|
227
309
|
(yield db_1.XenonDatabase.DeviceModel)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CLAIM_RESET = exports.UNHELD = exports.NO_CLAIM = exports.PENDING_CLAIM_TIMEOUT_MS = void 0;
|
|
4
|
+
exports.isPendingClaim = isPendingClaim;
|
|
5
|
+
exports.pendingClaimExpired = pendingClaimExpired;
|
|
6
|
+
exports.claimWhere = claimWhere;
|
|
7
|
+
exports.holdsClaim = holdsClaim;
|
|
8
|
+
exports.hasNoClaim = hasNoClaim;
|
|
9
|
+
exports.isUnheld = isUnheld;
|
|
10
|
+
exports.heldHere = heldHere;
|
|
11
|
+
/**
|
|
12
|
+
* Why a phone is busy, on the server that allocates it.
|
|
13
|
+
*
|
|
14
|
+
* Allocation reads `busy` (findAndLockDevice takes a phone only where it is
|
|
15
|
+
* false, in one conditional update). On a hub a node's phone is busy for one
|
|
16
|
+
* of two reasons, each kept in its own columns so neither can erase the
|
|
17
|
+
* other:
|
|
18
|
+
*
|
|
19
|
+
* - **The claim**, this server's: `claimedAt` (when allocation took the phone)
|
|
20
|
+
* and `claimSessionId` (the session's id, written when the session exists).
|
|
21
|
+
* A claim with no session id yet is *pending*: its session is being
|
|
22
|
+
* created, which can take minutes on a first driver install.
|
|
23
|
+
* - **The node's report**: `nodeBusy`, what the node last said
|
|
24
|
+
* (`POST /xenon/api/register`). Always false for a phone this server drives.
|
|
25
|
+
*
|
|
26
|
+
* `busy` is claim OR report. No write that can clear it reads the row first:
|
|
27
|
+
*
|
|
28
|
+
* - A node reporting its phone free clears `busy` only where nothing of the
|
|
29
|
+
* hub's holds it (UNHELD: no claim, and no hold such as a preview or a
|
|
30
|
+
* recording in `session_id`), in one conditional update. A report sent
|
|
31
|
+
* before the hub's claim (every 30 s by default) can no longer undo it.
|
|
32
|
+
* - A release names the claim it ends (ClaimRef): a session's id, or when a
|
|
33
|
+
* pending claim was taken. A stale release, for a session that ended while
|
|
34
|
+
* the phone went on to another, matches nothing. Then `busy` is cleared
|
|
35
|
+
* only where nothing else holds the phone (UNHELD), again one conditional
|
|
36
|
+
* update: a node still reporting it busy keeps it busy.
|
|
37
|
+
* - The idle sweeper leaves a pending claim alone until
|
|
38
|
+
* PENDING_CLAIM_TIMEOUT_MS, whatever the new-command timeout.
|
|
39
|
+
*
|
|
40
|
+
* A server with no nodes (standalone, or a node itself) never has `nodeBusy`
|
|
41
|
+
* set, so `busy` is its claim as it always was.
|
|
42
|
+
*
|
|
43
|
+
* Only a session's allocation takes a claim. A lease locks with `busy` alone,
|
|
44
|
+
* as before; a manual hold (a preview, a recording) writes `session_id`.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* How long a claim may wait for its session to be created before the idle
|
|
48
|
+
* sweeper frees it: a create that never finished (the hub restarted during
|
|
49
|
+
* it, or it hung). Well past a create's own budget: the phone's wait
|
|
50
|
+
* (`deviceAvailabilityTimeoutMs`) comes before the claim, and a first
|
|
51
|
+
* UiAutomator2 or WebDriverAgent install fits in a few minutes.
|
|
52
|
+
*/
|
|
53
|
+
exports.PENDING_CLAIM_TIMEOUT_MS = 10 * 60000;
|
|
54
|
+
/** A claim whose session is still being created. */
|
|
55
|
+
function isPendingClaim(device) {
|
|
56
|
+
return device.claimedAt != null && device.claimSessionId == null;
|
|
57
|
+
}
|
|
58
|
+
/** A pending claim older than PENDING_CLAIM_TIMEOUT_MS. */
|
|
59
|
+
function pendingClaimExpired(device, now) {
|
|
60
|
+
return isPendingClaim(device) && now - device.claimedAt > exports.PENDING_CLAIM_TIMEOUT_MS;
|
|
61
|
+
}
|
|
62
|
+
/** Prisma `where` (beside udid and host) for the claim `ref` names. */
|
|
63
|
+
function claimWhere(ref) {
|
|
64
|
+
if ('sessionId' in ref) {
|
|
65
|
+
return {
|
|
66
|
+
OR: [{ claimSessionId: ref.sessionId }, { claimSessionId: null, session_id: ref.sessionId }],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (ref.claimedAt == null)
|
|
70
|
+
return { claimSessionId: null, claimedAt: null, session_id: null };
|
|
71
|
+
return { claimSessionId: null, claimedAt: ref.claimedAt };
|
|
72
|
+
}
|
|
73
|
+
/** claimWhere as a predicate, for the in-memory store. */
|
|
74
|
+
function holdsClaim(device, ref) {
|
|
75
|
+
if ('sessionId' in ref) {
|
|
76
|
+
return (device.claimSessionId === ref.sessionId ||
|
|
77
|
+
(device.claimSessionId == null && device.session_id === ref.sessionId));
|
|
78
|
+
}
|
|
79
|
+
if (device.claimSessionId != null)
|
|
80
|
+
return false;
|
|
81
|
+
if (ref.claimedAt == null)
|
|
82
|
+
return device.claimedAt == null && device.session_id == null;
|
|
83
|
+
return device.claimedAt === ref.claimedAt;
|
|
84
|
+
}
|
|
85
|
+
/** No claim of this server's on the phone, pending or not. */
|
|
86
|
+
exports.NO_CLAIM = { claimSessionId: null, claimedAt: null };
|
|
87
|
+
function hasNoClaim(device) {
|
|
88
|
+
return device.claimSessionId == null && device.claimedAt == null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Nothing holds the phone: no claim, no session or manual hold, and no node
|
|
92
|
+
* reporting it busy. Only then may a release clear `busy`.
|
|
93
|
+
*/
|
|
94
|
+
exports.UNHELD = Object.assign(Object.assign({}, exports.NO_CLAIM), { session_id: null, nodeBusy: false });
|
|
95
|
+
function isUnheld(device) {
|
|
96
|
+
return hasNoClaim(device) && device.session_id == null && device.nodeBusy !== true;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Busy for a reason this server has on record: its claim, a session or hold
|
|
100
|
+
* in `session_id`, or plain `busy` on a phone of its own. False for a node's
|
|
101
|
+
* phone busy only by the node's report (`nodeBusy`), a preview the node
|
|
102
|
+
* holds say: this server knows no holder for it, and the node, which does,
|
|
103
|
+
* judges every call forwarded there with its own guard. An ownership check
|
|
104
|
+
* reads this, not `busy`, or it fails closed on every such phone, refusing
|
|
105
|
+
* the preview's own holder. A server with no nodes never has nodeBusy, so
|
|
106
|
+
* for it this is `busy`.
|
|
107
|
+
*/
|
|
108
|
+
function heldHere(device) {
|
|
109
|
+
if (!device.busy)
|
|
110
|
+
return false;
|
|
111
|
+
if (device.nodeBusy !== true)
|
|
112
|
+
return true;
|
|
113
|
+
return device.session_id != null || device.claimedAt != null || device.claimSessionId != null;
|
|
114
|
+
}
|
|
115
|
+
/** What ending a claim resets: the claim, and the session's bookkeeping. */
|
|
116
|
+
exports.CLAIM_RESET = Object.assign(Object.assign({}, exports.NO_CLAIM), { session_id: null, lastCmdExecutedAt: null, sessionStartTime: 0, newCommandTimeout: null, sessionProgress: '', owningSessionId: null, lockedAt: null });
|
|
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DISCOVERY_FIELDS = void 0;
|
|
6
|
+
exports.NODE_REPORT_FIELDS = exports.DISCOVERY_FIELDS = void 0;
|
|
7
7
|
exports.pickDiscoveryFields = pickDiscoveryFields;
|
|
8
|
+
exports.pickNodeReportFields = pickNodeReportFields;
|
|
8
9
|
exports.discoveryChanges = discoveryChanges;
|
|
9
10
|
const lodash_1 = __importDefault(require("lodash"));
|
|
10
11
|
/**
|
|
@@ -12,12 +13,21 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
12
13
|
*
|
|
13
14
|
* The periodic device sync reads every row, spends seconds in adb or simctl,
|
|
14
15
|
* then writes each phone back. For a phone it already knows it may write only
|
|
15
|
-
* what discovery itself observes (`discovery`). Every
|
|
16
|
+
* what discovery itself observes (`discovery`). Every other column has
|
|
16
17
|
* another writer: a session's lock, a preview's hold, an admin's block,
|
|
17
18
|
* reservation or team, a counter, the health check. A sync that read the row
|
|
18
19
|
* before one of those writes would put the old value back. That erased a live
|
|
19
20
|
* preview hold on the lab, and could hand a phone in a session to a second one.
|
|
20
21
|
*
|
|
22
|
+
* A hub also stores its nodes' phones, from each node's report
|
|
23
|
+
* (`POST /xenon/api/register`, NODE_REPORT_FIELDS). The report carries what
|
|
24
|
+
* the node observes of its own phone: `discovery`, and `observed` (its health,
|
|
25
|
+
* which only the server driving the phone can check). It never writes a
|
|
26
|
+
* `runtime` column: the hub's claim for a session, and the settings the hub
|
|
27
|
+
* owns for the phone (team, tags, reservation, block). Whether the node says
|
|
28
|
+
* the phone is busy goes to `nodeBusy` (`report`), never to `busy`
|
|
29
|
+
* (deviceClaims.ts).
|
|
30
|
+
*
|
|
21
31
|
* Typed over every column, so a new column won't compile until it is placed.
|
|
22
32
|
*/
|
|
23
33
|
const OWNER = {
|
|
@@ -59,8 +69,9 @@ const OWNER = {
|
|
|
59
69
|
newCommandTimeout: 'runtime',
|
|
60
70
|
owningSessionId: 'runtime',
|
|
61
71
|
lockedAt: 'runtime',
|
|
72
|
+
claimSessionId: 'runtime',
|
|
73
|
+
claimedAt: 'runtime',
|
|
62
74
|
userBlocked: 'runtime',
|
|
63
|
-
offline: 'runtime',
|
|
64
75
|
reservationReason: 'runtime',
|
|
65
76
|
reservedBy: 'runtime',
|
|
66
77
|
reservedUntil: 'runtime',
|
|
@@ -68,14 +79,16 @@ const OWNER = {
|
|
|
68
79
|
tags: 'runtime',
|
|
69
80
|
totalUtilizationTimeMilliSec: 'runtime',
|
|
70
81
|
total_session_count: 'runtime',
|
|
71
|
-
totalHealedCount: 'runtime',
|
|
72
82
|
dashboard_link: 'runtime',
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
offline: 'observed',
|
|
84
|
+
totalHealedCount: 'observed',
|
|
85
|
+
healthStatus: 'observed',
|
|
86
|
+
healthCheckError: 'observed',
|
|
87
|
+
lastHealthCheckAt: 'observed',
|
|
88
|
+
batteryLevel: 'observed',
|
|
89
|
+
thermalStatus: 'observed',
|
|
90
|
+
storageFree: 'observed',
|
|
91
|
+
nodeBusy: 'report',
|
|
79
92
|
};
|
|
80
93
|
/** The columns a sync may write to a phone it already knows. */
|
|
81
94
|
exports.DISCOVERY_FIELDS = new Set(Object.keys(OWNER).filter((column) => OWNER[column] === 'discovery'));
|
|
@@ -83,6 +96,15 @@ exports.DISCOVERY_FIELDS = new Set(Object.keys(OWNER).filter((column) => OWNER[c
|
|
|
83
96
|
function pickDiscoveryFields(data) {
|
|
84
97
|
return lodash_1.default.pickBy(data, (_value, key) => exports.DISCOVERY_FIELDS.has(key));
|
|
85
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* The columns a hub takes from a node's report of a phone: what the node
|
|
101
|
+
* observes of it. Not `nodeBusy`, which the hub sets from the report's `busy`.
|
|
102
|
+
*/
|
|
103
|
+
exports.NODE_REPORT_FIELDS = new Set(Object.keys(OWNER).filter((column) => OWNER[column] === 'discovery' || OWNER[column] === 'observed'));
|
|
104
|
+
/** Only the columns of `data` a hub takes from a node's report. */
|
|
105
|
+
function pickNodeReportFields(data) {
|
|
106
|
+
return lodash_1.default.pickBy(data, (_value, key) => exports.NODE_REPORT_FIELDS.has(key));
|
|
107
|
+
}
|
|
86
108
|
/**
|
|
87
109
|
* What a sync writes to a phone it already knows: the discovery columns whose
|
|
88
110
|
* value differs from `known`, the row the sync started from. Discovery hands
|