@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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.LiveSessionOwners = void 0;
|
|
10
|
+
const typedi_1 = require("typedi");
|
|
11
|
+
/** Far more live sessions than one server runs; bounds a leak if an end is missed. */
|
|
12
|
+
const MAX_LIVE_SESSIONS = 1000;
|
|
13
|
+
/**
|
|
14
|
+
* The owners of the sessions this server runs, while they run.
|
|
15
|
+
*
|
|
16
|
+
* SessionOwnerResolver reads a session's owner from its Session row. A node
|
|
17
|
+
* writes none for a session the hub created (the hub keeps that record, in
|
|
18
|
+
* its own database), so the node's own ownership checks (the /control guard,
|
|
19
|
+
* the logcat WebSocket, the session listing) found no owner and, failing
|
|
20
|
+
* closed, refused the phone to everyone but admins, its owner included. The
|
|
21
|
+
* same held for any server with the dashboard off.
|
|
22
|
+
*
|
|
23
|
+
* finalizeSession records the owner of each session this server drives (on a
|
|
24
|
+
* node, the one the hub's create token named), and the session's end forgets
|
|
25
|
+
* it: deleteSession, an unexpected shutdown, or the server's own shutdown.
|
|
26
|
+
* SessionOwnerResolver asks here before the database.
|
|
27
|
+
*/
|
|
28
|
+
let LiveSessionOwners = class LiveSessionOwners {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.owners = new Map();
|
|
31
|
+
}
|
|
32
|
+
record(sessionId, userId) {
|
|
33
|
+
if (!sessionId || !userId)
|
|
34
|
+
return;
|
|
35
|
+
this.owners.delete(sessionId);
|
|
36
|
+
if (this.owners.size >= MAX_LIVE_SESSIONS) {
|
|
37
|
+
const oldest = this.owners.keys().next().value;
|
|
38
|
+
this.owners.delete(oldest);
|
|
39
|
+
}
|
|
40
|
+
this.owners.set(sessionId, userId);
|
|
41
|
+
}
|
|
42
|
+
forget(sessionId) {
|
|
43
|
+
if (sessionId)
|
|
44
|
+
this.owners.delete(sessionId);
|
|
45
|
+
}
|
|
46
|
+
ownerOf(sessionId) {
|
|
47
|
+
return sessionId ? this.owners.get(sessionId) : undefined;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.LiveSessionOwners = LiveSessionOwners;
|
|
51
|
+
exports.LiveSessionOwners = LiveSessionOwners = __decorate([
|
|
52
|
+
(0, typedi_1.Service)()
|
|
53
|
+
], LiveSessionOwners);
|
|
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.SessionOwnerResolver = void 0;
|
|
22
22
|
const typedi_1 = require("typedi");
|
|
23
23
|
const prisma_1 = require("../../prisma");
|
|
24
|
+
const LiveSessionOwners_1 = require("./LiveSessionOwners");
|
|
24
25
|
/** Bound so a long-lived server cannot accumulate entries without limit. */
|
|
25
26
|
const MAX_CACHE_ENTRIES = 500;
|
|
26
27
|
/**
|
|
@@ -35,12 +36,18 @@ const MAX_CACHE_ENTRIES = 500;
|
|
|
35
36
|
* given session id, so a resolved owner cannot go stale — but a *negative*
|
|
36
37
|
* result can simply mean the Session row has not been written yet, and caching
|
|
37
38
|
* that would deny the owner their own device for the life of the process.
|
|
39
|
+
*
|
|
40
|
+
* A live session this server drives is looked up in memory first
|
|
41
|
+
* (LiveSessionOwners): a node writes no row for a session the hub created, and
|
|
42
|
+
* a server with the dashboard off writes none at all.
|
|
38
43
|
*/
|
|
39
44
|
let SessionOwnerResolver = class SessionOwnerResolver {
|
|
40
45
|
constructor(db = prisma_1.prisma) {
|
|
41
46
|
this.db = db;
|
|
42
47
|
this.ownerCache = new Map();
|
|
43
48
|
this.nameCache = new Map();
|
|
49
|
+
/** The owners of this server's live sessions. A field, not a constructor parameter: TypeDI. */
|
|
50
|
+
this.liveOwners = () => typedi_1.Container.get(LiveSessionOwners_1.LiveSessionOwners);
|
|
44
51
|
}
|
|
45
52
|
ownerOf(sessionId) {
|
|
46
53
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -50,6 +57,11 @@ let SessionOwnerResolver = class SessionOwnerResolver {
|
|
|
50
57
|
const cached = this.ownerCache.get(sessionId);
|
|
51
58
|
if (cached)
|
|
52
59
|
return cached;
|
|
60
|
+
const live = this.liveOwners().ownerOf(sessionId);
|
|
61
|
+
if (live) {
|
|
62
|
+
this.remember(this.ownerCache, sessionId, live);
|
|
63
|
+
return live;
|
|
64
|
+
}
|
|
53
65
|
const session = yield this.db.session.findUnique({
|
|
54
66
|
where: { id: sessionId },
|
|
55
67
|
select: { api_key_id: true, user_id: true },
|
|
@@ -70,6 +82,68 @@ let SessionOwnerResolver = class SessionOwnerResolver {
|
|
|
70
82
|
return owner;
|
|
71
83
|
});
|
|
72
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* The owner of each of several sessions, by exactly ownerOf's rule, in one
|
|
87
|
+
* query: one `session.findMany` for the ids not already cached, plus one
|
|
88
|
+
* `apiKey.findMany` only if some of those rows predate `user_id`. Used to
|
|
89
|
+
* filter Appium's session listing, where an ownerOf per listed session would
|
|
90
|
+
* be a query per session.
|
|
91
|
+
*
|
|
92
|
+
* Every requested id is in the result, null when no owner is on record.
|
|
93
|
+
* Shares ownerOf's cache, with the same rule: only resolved owners are kept.
|
|
94
|
+
* A failed query rejects; nothing is guessed.
|
|
95
|
+
*/
|
|
96
|
+
ownersOf(sessionIds) {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
var _a, _b, _c;
|
|
99
|
+
const ids = [...new Set(sessionIds)];
|
|
100
|
+
const resolved = new Map();
|
|
101
|
+
const missing = [];
|
|
102
|
+
for (const id of ids) {
|
|
103
|
+
const cached = id ? ((_a = this.ownerCache.get(id)) !== null && _a !== void 0 ? _a : this.liveOwners().ownerOf(id)) : undefined;
|
|
104
|
+
if (cached) {
|
|
105
|
+
resolved.set(id, cached);
|
|
106
|
+
this.remember(this.ownerCache, id, cached);
|
|
107
|
+
}
|
|
108
|
+
else if (id)
|
|
109
|
+
missing.push(id);
|
|
110
|
+
else
|
|
111
|
+
resolved.set(id, null);
|
|
112
|
+
}
|
|
113
|
+
if (missing.length > 0) {
|
|
114
|
+
const rows = yield this.db.session.findMany({
|
|
115
|
+
where: { id: { in: missing } },
|
|
116
|
+
select: { id: true, api_key_id: true, user_id: true },
|
|
117
|
+
});
|
|
118
|
+
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
119
|
+
// Rows written before user_id existed resolve through the key, as in ownerOf.
|
|
120
|
+
const legacyKeyIds = [
|
|
121
|
+
...new Set(rows
|
|
122
|
+
.filter((row) => !row.user_id && row.api_key_id)
|
|
123
|
+
.map((row) => row.api_key_id)),
|
|
124
|
+
];
|
|
125
|
+
const keyOwner = new Map();
|
|
126
|
+
if (legacyKeyIds.length > 0) {
|
|
127
|
+
const keys = yield this.db.apiKey.findMany({
|
|
128
|
+
where: { id: { in: legacyKeyIds } },
|
|
129
|
+
select: { id: true, userId: true },
|
|
130
|
+
});
|
|
131
|
+
for (const key of keys)
|
|
132
|
+
if (key.userId)
|
|
133
|
+
keyOwner.set(key.id, key.userId);
|
|
134
|
+
}
|
|
135
|
+
for (const id of missing) {
|
|
136
|
+
const row = byId.get(id);
|
|
137
|
+
const owner = (_b = row === null || row === void 0 ? void 0 : row.user_id) !== null && _b !== void 0 ? _b : ((row === null || row === void 0 ? void 0 : row.api_key_id) ? ((_c = keyOwner.get(row.api_key_id)) !== null && _c !== void 0 ? _c : null) : null);
|
|
138
|
+
resolved.set(id, owner);
|
|
139
|
+
if (owner)
|
|
140
|
+
this.remember(this.ownerCache, id, owner);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// In the order asked, so a caller filtering a list keeps its order.
|
|
144
|
+
return new Map(ids.map((id) => { var _a; return [id, (_a = resolved.get(id)) !== null && _a !== void 0 ? _a : null]; }));
|
|
145
|
+
});
|
|
146
|
+
}
|
|
73
147
|
displayName(userId) {
|
|
74
148
|
return __awaiter(this, void 0, void 0, function* () {
|
|
75
149
|
var _a, _b;
|
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.makeTicketActorAuthorizer = makeTicketActorAuthorizer;
|
|
13
13
|
const manualLock_1 = require("../recording/manualLock");
|
|
14
14
|
const deviceAccessPolicy_1 = require("./deviceAccessPolicy");
|
|
15
|
+
const deviceClaims_1 = require("../../data-service/deviceClaims");
|
|
15
16
|
/**
|
|
16
17
|
* Build the ownership check for a caller authenticated by a stream ticket.
|
|
17
18
|
*
|
|
@@ -54,7 +55,8 @@ function makeTicketActorAuthorizer(deps) {
|
|
|
54
55
|
: null;
|
|
55
56
|
return (0, deviceAccessPolicy_1.evaluateDeviceAccess)({
|
|
56
57
|
udid,
|
|
57
|
-
|
|
58
|
+
// heldHere, as deviceAccessGuard reads it.
|
|
59
|
+
busy: (0, deviceClaims_1.heldHere)(device),
|
|
58
60
|
sessionId: device.session_id,
|
|
59
61
|
sessionOwnerUserId,
|
|
60
62
|
// The ticket's actorId is a User id — see StreamTicketService.
|
|
@@ -22,10 +22,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
exports.LeaseOrphanSweeper = void 0;
|
|
25
|
+
exports.leaseReapReason = leaseReapReason;
|
|
25
26
|
const typedi_1 = require("typedi");
|
|
26
27
|
const prisma_1 = require("../../prisma");
|
|
27
28
|
const device_store_1 = require("../../data-service/device-store");
|
|
28
29
|
const logger_1 = __importDefault(require("../../logger"));
|
|
30
|
+
/**
|
|
31
|
+
* Why an active lease is over, or null while it isn't: its client missed
|
|
32
|
+
* three heartbeats, or it passed expiresAt. The second is the rule
|
|
33
|
+
* LeaseService already applies (heartbeat and extend refuse such a lease,
|
|
34
|
+
* authorizeSessionUse won't resolve it); without it here, a client that kept
|
|
35
|
+
* heartbeating held the phone for up to 3 x heartbeatSeconds past the end.
|
|
36
|
+
*/
|
|
37
|
+
function leaseReapReason(lease, now) {
|
|
38
|
+
var _a;
|
|
39
|
+
// The end time first: past it, heartbeats are refused, so a lease that
|
|
40
|
+
// expired has missed its heartbeats too by the next sweep.
|
|
41
|
+
if (lease.expiresAt < now)
|
|
42
|
+
return `expired at ${new Date(lease.expiresAt).toISOString()}`;
|
|
43
|
+
const thresholdMs = ((_a = lease.heartbeatSeconds) !== null && _a !== void 0 ? _a : 30) * 1000 * 3;
|
|
44
|
+
if (lease.lastHeartbeatAt + thresholdMs < now)
|
|
45
|
+
return 'missed heartbeats';
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
29
48
|
let LeaseOrphanSweeper = class LeaseOrphanSweeper {
|
|
30
49
|
constructor(db = prisma_1.prisma, store = device_store_1.DeviceStoreFactory.getStore()) {
|
|
31
50
|
this.db = db;
|
|
@@ -34,21 +53,30 @@ let LeaseOrphanSweeper = class LeaseOrphanSweeper {
|
|
|
34
53
|
}
|
|
35
54
|
sweep() {
|
|
36
55
|
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
-
var _a;
|
|
38
56
|
const now = Date.now();
|
|
39
57
|
const candidates = yield this.db.lease.findMany({
|
|
40
58
|
where: { status: 'active' },
|
|
41
|
-
select: {
|
|
59
|
+
select: {
|
|
60
|
+
id: true,
|
|
61
|
+
deviceUdid: true,
|
|
62
|
+
deviceHost: true,
|
|
63
|
+
heartbeatSeconds: true,
|
|
64
|
+
lastHeartbeatAt: true,
|
|
65
|
+
expiresAt: true,
|
|
66
|
+
},
|
|
42
67
|
});
|
|
43
68
|
for (const lease of candidates) {
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
69
|
+
const reason = leaseReapReason(lease, now);
|
|
70
|
+
if (!reason)
|
|
46
71
|
continue;
|
|
47
72
|
try {
|
|
48
73
|
yield this.db.lease.update({ where: { id: lease.id }, data: { status: 'expired' } });
|
|
49
74
|
yield this.db.portLease.deleteMany({ where: { leaseId: lease.id } });
|
|
50
|
-
|
|
51
|
-
|
|
75
|
+
// Only the lease's lock: a session that outlived its lease keeps the
|
|
76
|
+
// phone, and its own release frees it.
|
|
77
|
+
const freed = yield this.store.releaseLeaseLock(lease.deviceUdid, lease.deviceHost);
|
|
78
|
+
const device = `${lease.deviceUdid}@${lease.deviceHost}`;
|
|
79
|
+
this.logger.info(`reaped lease ${lease.id} (${reason}); device ${device} ${freed ? 'unblocked' : 'still held (a session or a hold), left busy'}`);
|
|
52
80
|
}
|
|
53
81
|
catch (err) {
|
|
54
82
|
this.logger.warn(`failed to reap lease ${lease.id}: ${err.message}`);
|
|
@@ -279,13 +279,26 @@ let LeaseService = class LeaseService {
|
|
|
279
279
|
return lease;
|
|
280
280
|
});
|
|
281
281
|
}
|
|
282
|
-
|
|
282
|
+
/**
|
|
283
|
+
* The lease, as loadActiveLease, if it also hasn't passed its expiresAt.
|
|
284
|
+
* The sweeper marks leases by missed heartbeats, not by expiresAt, so a
|
|
285
|
+
* lapsed lease can still read 'active'. It is gone all the same: heartbeat
|
|
286
|
+
* must not keep it and extend must not revive it. Release still takes it,
|
|
287
|
+
* so its holder can hand the device back early.
|
|
288
|
+
*/
|
|
289
|
+
loadLiveLease(leaseId, token) {
|
|
283
290
|
return __awaiter(this, void 0, void 0, function* () {
|
|
284
291
|
const lease = yield this.loadActiveLease(leaseId, token);
|
|
285
|
-
|
|
286
|
-
if (lease.expiresAt < now) {
|
|
292
|
+
if (lease.expiresAt < Date.now()) {
|
|
287
293
|
throw new LeaseGone(`lease ${leaseId} expired at ${lease.expiresAt}`);
|
|
288
294
|
}
|
|
295
|
+
return lease;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
heartbeat(leaseId, token) {
|
|
299
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
300
|
+
const lease = yield this.loadLiveLease(leaseId, token);
|
|
301
|
+
const now = Date.now();
|
|
289
302
|
// Guarded by status='active' so a concurrent sweeper expiration is observed.
|
|
290
303
|
const result = yield this.db.lease.updateMany({
|
|
291
304
|
where: { id: leaseId, status: 'active' },
|
|
@@ -299,7 +312,7 @@ let LeaseService = class LeaseService {
|
|
|
299
312
|
}
|
|
300
313
|
extend(leaseId, token, additionalMs) {
|
|
301
314
|
return __awaiter(this, void 0, void 0, function* () {
|
|
302
|
-
const lease = yield this.
|
|
315
|
+
const lease = yield this.loadLiveLease(leaseId, token);
|
|
303
316
|
const now = Date.now();
|
|
304
317
|
const createdAtMs = typeof lease.createdAt === 'number' ? lease.createdAt : new Date(lease.createdAt).getTime();
|
|
305
318
|
const ceiling = createdAtMs + MAX_LEASE_MS;
|
|
@@ -325,7 +338,8 @@ let LeaseService = class LeaseService {
|
|
|
325
338
|
// idempotent and safe — port rows may already be gone, device may already
|
|
326
339
|
// be unlocked. We treat that as a successful release.
|
|
327
340
|
yield this.db.portLease.deleteMany({ where: { leaseId } });
|
|
328
|
-
|
|
341
|
+
// Only the lease's lock: a session still on the phone keeps it busy.
|
|
342
|
+
yield this.store.releaseLeaseLock(lease.deviceUdid, lease.deviceHost);
|
|
329
343
|
void result;
|
|
330
344
|
});
|
|
331
345
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.capsForNode = capsForNode;
|
|
7
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
|
+
const sessionCredentials_1 = require("./sessionCredentials");
|
|
9
|
+
const xenonOptions_1 = require("./xenonOptions");
|
|
10
|
+
/**
|
|
11
|
+
* The capabilities a hub sends the node whose phone it allocated.
|
|
12
|
+
*
|
|
13
|
+
* Each instance has its own database, and the hub has already done what
|
|
14
|
+
* needs its database: it checked the credentials, applied the team rule and
|
|
15
|
+
* resolved the lease to a phone. The node gets a request that stands on its
|
|
16
|
+
* own, whatever the node's auth settings: that one phone, and nothing the
|
|
17
|
+
* node can't act on.
|
|
18
|
+
*
|
|
19
|
+
* - No credentials. createSession took them out of `caps` first thing; they
|
|
20
|
+
* are removed here again so no copy that leaves the hub can carry one. The
|
|
21
|
+
* node gets the hub's create token instead (hubSessionToken.ts).
|
|
22
|
+
* - No lease id. The lease lives in the hub's database; a node that looked it
|
|
23
|
+
* up in its own would refuse the session.
|
|
24
|
+
* - The phone, pinned: `appium:udid` in alwaysMatch, and no `appium:udid` or
|
|
25
|
+
* `appium:udids` anywhere else. A lease-bound allocation does not write the
|
|
26
|
+
* udid into the caps, and a `udids` list would let the node pick another
|
|
27
|
+
* phone than the one the hub holds.
|
|
28
|
+
*
|
|
29
|
+
* `caps` is not modified: the hub keeps its own copy for its records.
|
|
30
|
+
*/
|
|
31
|
+
const NOT_FOR_THE_NODE = ['leaseId', ...sessionCredentials_1.SECRET_OPTION_FIELDS];
|
|
32
|
+
const DEVICE_PICKERS = ['appium:udid', 'appium:udids'];
|
|
33
|
+
function capsForNode(caps, udid) {
|
|
34
|
+
const alwaysMatch = (0, xenonOptions_1.isOptionsObject)(caps === null || caps === void 0 ? void 0 : caps.alwaysMatch)
|
|
35
|
+
? lodash_1.default.cloneDeep(caps.alwaysMatch)
|
|
36
|
+
: {};
|
|
37
|
+
const firstMatch = Array.isArray(caps === null || caps === void 0 ? void 0 : caps.firstMatch) && caps.firstMatch.length > 0
|
|
38
|
+
? caps.firstMatch.map((entry) => ((0, xenonOptions_1.isOptionsObject)(entry) ? lodash_1.default.cloneDeep(entry) : entry))
|
|
39
|
+
: [{}];
|
|
40
|
+
for (const bucket of [alwaysMatch, ...firstMatch]) {
|
|
41
|
+
if (!(0, xenonOptions_1.isOptionsObject)(bucket))
|
|
42
|
+
continue;
|
|
43
|
+
for (const key of DEVICE_PICKERS)
|
|
44
|
+
delete bucket[key];
|
|
45
|
+
for (const namespace of xenonOptions_1.OPTION_NAMESPACES) {
|
|
46
|
+
const options = bucket[namespace];
|
|
47
|
+
if (!(0, xenonOptions_1.isOptionsObject)(options))
|
|
48
|
+
continue;
|
|
49
|
+
for (const field of NOT_FOR_THE_NODE)
|
|
50
|
+
delete options[field];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
alwaysMatch['appium:udid'] = udid;
|
|
54
|
+
return Object.assign(Object.assign({}, caps), { alwaysMatch, firstMatch });
|
|
55
|
+
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SECRET_OPTION_FIELDS = void 0;
|
|
4
4
|
exports.takeSessionCredentials = takeSessionCredentials;
|
|
5
|
-
exports.capsWithCredentials = capsWithCredentials;
|
|
6
5
|
const XenonCapabilityManager_1 = require("../../XenonCapabilityManager");
|
|
7
6
|
const xenonOptions_1 = require("./xenonOptions");
|
|
8
7
|
/**
|
|
@@ -15,7 +14,8 @@ const xenonOptions_1 = require("./xenonOptions");
|
|
|
15
14
|
* them out of the capabilities, in place, before anything else reads them:
|
|
16
15
|
* the pending-session row, device allocation, the driver, the Session row,
|
|
17
16
|
* the dashboard and the logs. Every other option, the lease id included,
|
|
18
|
-
* stays: a lease id is not a secret.
|
|
17
|
+
* stays: a lease id is not a secret. They never leave the server they were
|
|
18
|
+
* sent to.
|
|
19
19
|
*/
|
|
20
20
|
exports.SECRET_OPTION_FIELDS = ['accessKey', 'token', 'sessionToken', 'leaseToken'];
|
|
21
21
|
function buckets(caps) {
|
|
@@ -27,8 +27,11 @@ function buckets(caps) {
|
|
|
27
27
|
* Read the session's credentials, then remove every secret field from both
|
|
28
28
|
* namespaces in alwaysMatch and every firstMatch entry, in place: Appium hands
|
|
29
29
|
* this same object to the driver. The namespaces themselves are kept, emptied
|
|
30
|
-
* if need be,
|
|
31
|
-
*
|
|
30
|
+
* if need be, with the options that are not secrets.
|
|
31
|
+
*
|
|
32
|
+
* Nothing puts the credentials back. A hub forwarding a create to a node
|
|
33
|
+
* sends the hub's own create token instead (gateway/hubSessionToken.ts): each
|
|
34
|
+
* instance has its own database, so the node could not check them anyway.
|
|
32
35
|
*/
|
|
33
36
|
function takeSessionCredentials(caps) {
|
|
34
37
|
var _a;
|
|
@@ -48,58 +51,3 @@ function takeSessionCredentials(caps) {
|
|
|
48
51
|
}
|
|
49
52
|
return credentials;
|
|
50
53
|
}
|
|
51
|
-
/** The secret fields to put back, from what was read. */
|
|
52
|
-
function secretFields(credentials) {
|
|
53
|
-
const fields = {};
|
|
54
|
-
if (credentials.pair) {
|
|
55
|
-
fields.accessKey = credentials.pair.accessKey;
|
|
56
|
-
fields.token = credentials.pair.token;
|
|
57
|
-
}
|
|
58
|
-
if (credentials.sessionToken)
|
|
59
|
-
fields.sessionToken = credentials.sessionToken;
|
|
60
|
-
if (credentials.leaseToken)
|
|
61
|
-
fields.leaseToken = credentials.leaseToken;
|
|
62
|
-
return fields;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Where the winning namespace lives in the merged view: the bucket (the one
|
|
66
|
-
* that holds it, alwaysMatch first) and the namespace itself.
|
|
67
|
-
*/
|
|
68
|
-
function home(caps) {
|
|
69
|
-
const always = caps === null || caps === void 0 ? void 0 : caps.alwaysMatch;
|
|
70
|
-
const first = Array.isArray(caps === null || caps === void 0 ? void 0 : caps.firstMatch) ? caps.firstMatch[0] : undefined;
|
|
71
|
-
for (const namespace of xenonOptions_1.OPTION_NAMESPACES) {
|
|
72
|
-
for (const bucket of [always, first]) {
|
|
73
|
-
if (!(0, xenonOptions_1.isOptionsObject)(bucket) || !Object.prototype.hasOwnProperty.call(bucket, namespace)) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
// alwaysMatch holding the key hides firstMatch's, as in the merged view.
|
|
77
|
-
if ((0, xenonOptions_1.isOptionsObject)(bucket[namespace]))
|
|
78
|
-
return { bucket, namespace };
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* A copy of `caps` with the credentials put back, for a peer Xenon node. The
|
|
86
|
-
* node re-runs createSession: it authenticates and attributes the session,
|
|
87
|
-
* enforces XENON_REQUIRE_SESSION_TOKEN and checks the lease itself, so it
|
|
88
|
-
* needs exactly what this hub read. It takes them out again before its own
|
|
89
|
-
* driver sees them. They go into the namespace that wins, so the node reads
|
|
90
|
-
* the same values. `caps` is not modified. Never hand this to a cloud
|
|
91
|
-
* provider: it is not a Xenon node.
|
|
92
|
-
*/
|
|
93
|
-
function capsWithCredentials(caps, credentials) {
|
|
94
|
-
var _a;
|
|
95
|
-
const fields = secretFields(credentials);
|
|
96
|
-
const target = home(caps);
|
|
97
|
-
if (Object.keys(fields).length === 0 || !target)
|
|
98
|
-
return caps;
|
|
99
|
-
const { bucket, namespace } = target;
|
|
100
|
-
const withCredentials = Object.assign(Object.assign({}, bucket), { [namespace]: Object.assign(Object.assign({}, bucket[namespace]), fields) });
|
|
101
|
-
if (bucket === caps.alwaysMatch)
|
|
102
|
-
return Object.assign(Object.assign({}, caps), { alwaysMatch: withCredentials });
|
|
103
|
-
const [, ...rest] = (_a = caps.firstMatch) !== null && _a !== void 0 ? _a : [];
|
|
104
|
-
return Object.assign(Object.assign({}, caps), { firstMatch: [withCredentials, ...rest] });
|
|
105
|
-
}
|
|
@@ -19,6 +19,18 @@ class CloudSession extends RemoteSession_1.RemoteSession {
|
|
|
19
19
|
getType() {
|
|
20
20
|
return SessionType_1.default.CLOUD;
|
|
21
21
|
}
|
|
22
|
+
/** A cloud provider is not a Xenon node: it never gets the hub's session token. */
|
|
23
|
+
callOptions() {
|
|
24
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
+
return {};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** A cloud provider has no Xenon session-status route: the WebDriver probe it is. */
|
|
29
|
+
askNodeForSession() {
|
|
30
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
31
|
+
return null;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
22
34
|
getScreenShot() {
|
|
23
35
|
return __awaiter(this, void 0, void 0, function* () {
|
|
24
36
|
return '';
|
|
@@ -49,6 +49,7 @@ exports.LocalSession = void 0;
|
|
|
49
49
|
const AndroidDeviceManager_1 = __importDefault(require("../device-managers/AndroidDeviceManager"));
|
|
50
50
|
const logger_1 = __importDefault(require("../logger"));
|
|
51
51
|
const SessionType_1 = __importDefault(require("../enums/SessionType"));
|
|
52
|
+
const XenonSession_1 = require("./XenonSession");
|
|
52
53
|
const RemoteSession_1 = require("./RemoteSession");
|
|
53
54
|
const typedi_1 = require("typedi");
|
|
54
55
|
const device_managers_1 = require("../device-managers");
|
|
@@ -56,27 +57,57 @@ const XenonCapabilityManager_1 = require("../XenonCapabilityManager");
|
|
|
56
57
|
const VideoPipelineService_1 = require("../services/VideoPipelineService");
|
|
57
58
|
const AndroidStreamService_1 = __importDefault(require("../device-managers/android/AndroidStreamService"));
|
|
58
59
|
const IOSStreamService_1 = __importDefault(require("../device-managers/ios/IOSStreamService"));
|
|
59
|
-
|
|
60
|
-
if (!path || path == '') {
|
|
61
|
-
return '/wd-internal';
|
|
62
|
-
}
|
|
63
|
-
if (!path.startsWith('/')) {
|
|
64
|
-
path = `/${path}`;
|
|
65
|
-
}
|
|
66
|
-
if (path.endsWith('/')) {
|
|
67
|
-
path = path.substr(0, path.length - 2);
|
|
68
|
-
}
|
|
69
|
-
return `${path}/wd-internal`;
|
|
70
|
-
}
|
|
60
|
+
const internalCall_1 = require("../gateway/internalCall");
|
|
71
61
|
class LocalSession extends RemoteSession_1.RemoteSession {
|
|
72
62
|
constructor(options) {
|
|
73
63
|
const { address, port, basePath } = options.driver.opts || options.driver;
|
|
74
|
-
|
|
64
|
+
// The inherited HTTP calls loop back to this server's own
|
|
65
|
+
// `<basePath>/wd-internal`, which the session gateway accepts only with
|
|
66
|
+
// this process's secret (gateway/internalCall.ts).
|
|
67
|
+
super(Object.assign(Object.assign({}, options), { baseUrl: (0, internalCall_1.internalCallBaseUrl)(address, port, basePath) }));
|
|
75
68
|
this.driver = options.driver;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A loopback call carries the per-process secret, and never goes through an
|
|
72
|
+
* HTTP proxy from the environment, which would receive the secret.
|
|
73
|
+
*/
|
|
74
|
+
callOptions() {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
return { headers: (0, internalCall_1.internalCallHeaders)(), proxy: false };
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether Appium still has this session, asked of the in-process driver.
|
|
81
|
+
*
|
|
82
|
+
* Not an HTTP probe: any command sent to the session, `timeouts` included,
|
|
83
|
+
* restarts the driver's new-command timeout and Xenon's own idle clock, so a
|
|
84
|
+
* heartbeat every 30 s would keep a session its client abandoned (and its
|
|
85
|
+
* phone) alive for ever. The umbrella drops a session from its map when it
|
|
86
|
+
* is deleted, times out, or its driver shuts down unexpectedly.
|
|
87
|
+
*/
|
|
88
|
+
checkHealth() {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
var _a;
|
|
91
|
+
if (!this.sessionId) {
|
|
92
|
+
return {
|
|
93
|
+
isHealthy: false,
|
|
94
|
+
errorType: XenonSession_1.HealthErrorType.NONE,
|
|
95
|
+
message: 'No session ID assigned',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const umbrella = this.driver;
|
|
99
|
+
const exists = typeof (umbrella === null || umbrella === void 0 ? void 0 : umbrella.sessionExists) === 'function'
|
|
100
|
+
? !!umbrella.sessionExists(this.sessionId)
|
|
101
|
+
: !!((_a = umbrella === null || umbrella === void 0 ? void 0 : umbrella.sessions) === null || _a === void 0 ? void 0 : _a[this.sessionId]);
|
|
102
|
+
if (exists)
|
|
103
|
+
return { isHealthy: true, errorType: XenonSession_1.HealthErrorType.NONE };
|
|
104
|
+
return {
|
|
105
|
+
isHealthy: false,
|
|
106
|
+
errorType: XenonSession_1.HealthErrorType.SESSION_NOT_FOUND,
|
|
107
|
+
message: 'Appium no longer has this session',
|
|
108
|
+
statusCode: 404,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
80
111
|
}
|
|
81
112
|
/**
|
|
82
113
|
* A local session's driver is in this process, so ask it directly instead of
|
|
@@ -280,7 +311,7 @@ class LocalSession extends RemoteSession_1.RemoteSession {
|
|
|
280
311
|
isVideoRecordingInProgress() {
|
|
281
312
|
return typedi_1.Container.get(VideoPipelineService_1.VideoPipelineService).isRecording(this.sessionId);
|
|
282
313
|
}
|
|
283
|
-
//
|
|
314
|
+
// The in-process pipeline or driver first; the loopback call last.
|
|
284
315
|
stopVideoRecording(driver) {
|
|
285
316
|
const _super = Object.create(null, {
|
|
286
317
|
stopVideoRecording: { get: () => super.stopVideoRecording }
|
|
@@ -313,14 +344,9 @@ class LocalSession extends RemoteSession_1.RemoteSession {
|
|
|
313
344
|
catch (err) {
|
|
314
345
|
logger_1.default.warn(`[LocalSession] Direct stopRecordingScreen failed: ${err.message}. Falling back to HTTP.`);
|
|
315
346
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return yield _super.stopVideoRecording.call(this);
|
|
320
|
-
}
|
|
321
|
-
finally {
|
|
322
|
-
this.baseUrl = originalBaseUrl;
|
|
323
|
-
}
|
|
347
|
+
// Over HTTP to this server's own /wd-internal, with the secret: the public
|
|
348
|
+
// session path would need the owner's credentials under per-command auth.
|
|
349
|
+
return yield _super.stopVideoRecording.call(this);
|
|
324
350
|
});
|
|
325
351
|
}
|
|
326
352
|
getLiveVideoUrl() {
|