@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,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
require("reflect-metadata");
|
|
16
|
+
const chai_1 = require("chai");
|
|
17
|
+
const express_1 = __importDefault(require("express"));
|
|
18
|
+
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const http_1 = __importDefault(require("http"));
|
|
20
|
+
const os_1 = __importDefault(require("os"));
|
|
21
|
+
const path_1 = __importDefault(require("path"));
|
|
22
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
23
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
24
|
+
const typedi_1 = require("typedi");
|
|
25
|
+
const plugin_1 = require("../../src/plugin");
|
|
26
|
+
const registerCommandAuth_1 = require("../../src/app/registerCommandAuth");
|
|
27
|
+
const defaultGateway_1 = require("../../src/gateway/defaultGateway");
|
|
28
|
+
const hubSessionToken_1 = require("../../src/gateway/hubSessionToken");
|
|
29
|
+
const nodeSessionStatus_1 = require("../../src/gateway/nodeSessionStatus");
|
|
30
|
+
const JwtKeyService_1 = require("../../src/services/token/JwtKeyService");
|
|
31
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
32
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
33
|
+
const device_managers_1 = require("../../src/device-managers");
|
|
34
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
35
|
+
const prisma_store_1 = require("../../src/data-service/prisma-store");
|
|
36
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
37
|
+
const RemoteSession_1 = require("../../src/sessions/RemoteSession");
|
|
38
|
+
const appiumUmbrella_1 = require("../../src/sessions/appiumUmbrella");
|
|
39
|
+
const XenonSession_1 = require("../../src/sessions/XenonSession");
|
|
40
|
+
const config_1 = require("../../src/config");
|
|
41
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
42
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
43
|
+
const appium_umbrella_1 = require("../helpers/appium-umbrella");
|
|
44
|
+
/**
|
|
45
|
+
* The hub's heartbeat on a session a node runs (RemoteSession.checkHealth),
|
|
46
|
+
* against a node in Appium 3's own server(): Appium's umbrella with Xenon's
|
|
47
|
+
* plugin, the production node gateway, and the node's session-status route.
|
|
48
|
+
*
|
|
49
|
+
* The heartbeat runs every ~30 s. Sent as a WebDriver command, it restarted
|
|
50
|
+
* the node driver's new-command timeout and Xenon's idle clock, so a session
|
|
51
|
+
* its client abandoned was never ended, and its phone never freed. It asks the
|
|
52
|
+
* node's umbrella instead, which runs no command. An older node without that
|
|
53
|
+
* route is still probed the old way, and the hub says so once.
|
|
54
|
+
*/
|
|
55
|
+
const quiet = { info: () => undefined, warn: () => undefined, error: () => undefined };
|
|
56
|
+
const NODE_ID = 'node-1';
|
|
57
|
+
describe('the hub’s heartbeat on a node’s session', function () {
|
|
58
|
+
this.timeout(60000);
|
|
59
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
60
|
+
const servers = [];
|
|
61
|
+
let dirs;
|
|
62
|
+
let hubKeys;
|
|
63
|
+
let appiumLogs;
|
|
64
|
+
let restore;
|
|
65
|
+
let saved;
|
|
66
|
+
let authDisabledBefore;
|
|
67
|
+
let commandAuthBefore;
|
|
68
|
+
let umbrella;
|
|
69
|
+
let hubUrl;
|
|
70
|
+
let nodeOrigin;
|
|
71
|
+
before(() => __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
appiumLogs = (0, appium_umbrella_1.quietAppiumLogs)();
|
|
73
|
+
dirs = [fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-heartbeat-keys-'))];
|
|
74
|
+
hubKeys = new JwtKeyService_1.JwtKeyService();
|
|
75
|
+
yield hubKeys.init(dirs[0]);
|
|
76
|
+
}));
|
|
77
|
+
after(() => {
|
|
78
|
+
appiumLogs.restore();
|
|
79
|
+
for (const dir of dirs)
|
|
80
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
81
|
+
});
|
|
82
|
+
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
83
|
+
restore = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService, hubSessionToken_1.HubSessionTokenIssuer, device_managers_1.XenonManager, appiumUmbrella_1.AppiumUmbrella, nodeSessionStatus_1.NodeSessionProbeSupport);
|
|
84
|
+
// One process plays both sides: the hub's keys sign, the node checks them.
|
|
85
|
+
typedi_1.Container.set(JwtKeyService_1.JwtKeyService, hubKeys);
|
|
86
|
+
typedi_1.Container.set(hubSessionToken_1.HubSessionTokenIssuer, new hubSessionToken_1.HubSessionTokenIssuer());
|
|
87
|
+
typedi_1.Container.set(appiumUmbrella_1.AppiumUmbrella, new appiumUmbrella_1.AppiumUmbrella());
|
|
88
|
+
typedi_1.Container.set(nodeSessionStatus_1.NodeSessionProbeSupport, new nodeSessionStatus_1.NodeSessionProbeSupport());
|
|
89
|
+
typedi_1.Container.set(device_managers_1.XenonManager, {
|
|
90
|
+
getMaxSessionCount: () => undefined,
|
|
91
|
+
deviceInstances: () => __awaiter(this, void 0, void 0, function* () { return []; }),
|
|
92
|
+
});
|
|
93
|
+
const context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
94
|
+
saved = {
|
|
95
|
+
store: device_store_1.DeviceStoreFactory._deviceStore,
|
|
96
|
+
pending: device_store_1.DeviceStoreFactory._pendingSessionStore,
|
|
97
|
+
context: Object.assign({}, context),
|
|
98
|
+
};
|
|
99
|
+
device_store_1.DeviceStoreFactory._deviceStore = new prisma_store_1.PrismaDeviceStore();
|
|
100
|
+
device_store_1.DeviceStoreFactory._pendingSessionStore = new prisma_store_1.PrismaPendingSessionStore();
|
|
101
|
+
authDisabledBefore = config_1.config.authDisabled;
|
|
102
|
+
config_1.config.authDisabled = false;
|
|
103
|
+
// Per-command auth on: the node then takes the hub's token and nothing else.
|
|
104
|
+
commandAuthBefore = process.env.XENON_REQUIRE_COMMAND_AUTH;
|
|
105
|
+
process.env.XENON_REQUIRE_COMMAND_AUTH = '1';
|
|
106
|
+
yield scratch.db.pendingSession.deleteMany({});
|
|
107
|
+
yield scratch.db.device.deleteMany({});
|
|
108
|
+
}));
|
|
109
|
+
afterEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
for (const s of SessionManager_1.SESSION_MANAGER.getAllSessions())
|
|
111
|
+
SessionManager_1.SESSION_MANAGER.removeSession(s.getId());
|
|
112
|
+
device_store_1.DeviceStoreFactory._deviceStore = saved.store;
|
|
113
|
+
device_store_1.DeviceStoreFactory._pendingSessionStore = saved.pending;
|
|
114
|
+
Object.assign(typedi_1.Container.get(PluginContext_1.PluginContext), saved.context);
|
|
115
|
+
config_1.config.authDisabled = authDisabledBefore;
|
|
116
|
+
if (commandAuthBefore === undefined)
|
|
117
|
+
delete process.env.XENON_REQUIRE_COMMAND_AUTH;
|
|
118
|
+
else
|
|
119
|
+
process.env.XENON_REQUIRE_COMMAND_AUTH = commandAuthBefore;
|
|
120
|
+
sinon_1.default.restore();
|
|
121
|
+
restore();
|
|
122
|
+
for (const s of servers.splice(0)) {
|
|
123
|
+
s.closeAllConnections();
|
|
124
|
+
yield new Promise((resolve) => http_1.default.Server.prototype.close.call(s, () => resolve()));
|
|
125
|
+
}
|
|
126
|
+
}));
|
|
127
|
+
const port = (s) => s.address().port;
|
|
128
|
+
/** A hub (its public keys) and a node; `withRoute: false` is an older node. */
|
|
129
|
+
function boot() {
|
|
130
|
+
return __awaiter(this, arguments, void 0, function* ({ withRoute = true } = {}) {
|
|
131
|
+
const baseDriver = (0, appium_umbrella_1.appiumBaseDriver)();
|
|
132
|
+
const hubServer = yield baseDriver.server({
|
|
133
|
+
routeConfiguringFunction: baseDriver.routeConfiguringFunction((0, appium_umbrella_1.umbrellaWith)([])),
|
|
134
|
+
port: 0,
|
|
135
|
+
hostname: '127.0.0.1',
|
|
136
|
+
basePath: '/wd/hub',
|
|
137
|
+
cliArgs: { basePath: '/wd/hub' },
|
|
138
|
+
serverUpdaters: [
|
|
139
|
+
(app) => {
|
|
140
|
+
app.get('/xenon/api/auth/jwks.json', (_req, res) => res.json(hubKeys.jwks()));
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
servers.push(hubServer);
|
|
145
|
+
hubUrl = `http://127.0.0.1:${port(hubServer)}`;
|
|
146
|
+
const pluginArgs = Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { hub: hubUrl, bindHostOrIp: '127.0.0.1', enableDashboard: false, deviceAvailabilityTimeoutMs: 1000, deviceAvailabilityQueryIntervalMs: 100 });
|
|
147
|
+
umbrella = (0, appium_umbrella_1.umbrellaWith)([[plugin_1.XenonPlugin, 'xenon']]);
|
|
148
|
+
const nodeServer = yield baseDriver.server({
|
|
149
|
+
routeConfiguringFunction: baseDriver.routeConfiguringFunction(umbrella),
|
|
150
|
+
port: 0,
|
|
151
|
+
hostname: '127.0.0.1',
|
|
152
|
+
basePath: '/node',
|
|
153
|
+
cliArgs: { basePath: '/node' },
|
|
154
|
+
serverUpdaters: [
|
|
155
|
+
(app) => {
|
|
156
|
+
(0, registerCommandAuth_1.registerSessionGateway)(app, { basePath: '/node' }, (0, registerCommandAuth_1.commandAuthDeps)({ enabled: () => true, authDisabled: () => false, logger: quiet }), (0, defaultGateway_1.nodeGatewayOptions)(hubUrl));
|
|
157
|
+
// What ServerManager mounts under /xenon/api on a node.
|
|
158
|
+
const api = express_1.default.Router();
|
|
159
|
+
(0, nodeSessionStatus_1.registerNodeSessionStatus)(api, withRoute ? pluginArgs : Object.assign(Object.assign({}, pluginArgs), { hub: undefined }));
|
|
160
|
+
app.use('/xenon/api', api);
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
servers.push(nodeServer);
|
|
165
|
+
nodeOrigin = `http://127.0.0.1:${port(nodeServer)}`;
|
|
166
|
+
typedi_1.Container.get(PluginContext_1.PluginContext).setContext(pluginArgs, port(nodeServer), NODE_ID, '/node');
|
|
167
|
+
yield scratch.db.device.create({
|
|
168
|
+
data: {
|
|
169
|
+
udid: 'phone-1',
|
|
170
|
+
host: nodeOrigin,
|
|
171
|
+
nodeId: NODE_ID,
|
|
172
|
+
platform: 'android',
|
|
173
|
+
name: 'phone-1',
|
|
174
|
+
sdk: '14',
|
|
175
|
+
busy: false,
|
|
176
|
+
userBlocked: false,
|
|
177
|
+
offline: false,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/** A session on the node, created as the hub creates it. */
|
|
183
|
+
function nodeSession() {
|
|
184
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
185
|
+
const token = yield typedi_1.Container.get(hubSessionToken_1.HubSessionTokenIssuer).createTokenFor({
|
|
186
|
+
userId: 'alice',
|
|
187
|
+
udid: 'phone-1',
|
|
188
|
+
host: nodeOrigin,
|
|
189
|
+
});
|
|
190
|
+
const res = yield (0, loopbackRequest_1.default)(nodeOrigin)
|
|
191
|
+
.post('/node/session')
|
|
192
|
+
.set(hubSessionToken_1.HUB_TOKEN_HEADER, token)
|
|
193
|
+
.send({
|
|
194
|
+
capabilities: {
|
|
195
|
+
alwaysMatch: {
|
|
196
|
+
platformName: 'Android',
|
|
197
|
+
'appium:automationName': appium_umbrella_1.FAKE_AUTOMATION,
|
|
198
|
+
'appium:newCommandTimeout': 0,
|
|
199
|
+
'appium:udid': 'phone-1',
|
|
200
|
+
'xe:options': { recordVideo: false },
|
|
201
|
+
},
|
|
202
|
+
firstMatch: [{}],
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
206
|
+
return res.body.value.sessionId;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/** The hub's object for that session. */
|
|
210
|
+
const hubSide = (sessionId) => new RemoteSession_1.RemoteSession({
|
|
211
|
+
sessionId,
|
|
212
|
+
device: { udid: 'phone-1', host: nodeOrigin, nodeId: NODE_ID, platform: 'android' },
|
|
213
|
+
sessionResponse: {},
|
|
214
|
+
xenonOption: {},
|
|
215
|
+
baseUrl: `${nodeOrigin}/node`,
|
|
216
|
+
});
|
|
217
|
+
const idleClock = () => __awaiter(this, void 0, void 0, function* () { var _a; return (_a = (yield scratch.db.device.findFirst({ where: { udid: 'phone-1' } }))) === null || _a === void 0 ? void 0 : _a.lastCmdExecutedAt; });
|
|
218
|
+
it('asks the node without sending the session a command', () => __awaiter(this, void 0, void 0, function* () {
|
|
219
|
+
yield boot();
|
|
220
|
+
const sessionId = yield nodeSession();
|
|
221
|
+
const driver = umbrella.sessions[sessionId];
|
|
222
|
+
const restarted = sinon_1.default.spy(driver, 'startNewCommandTimeout');
|
|
223
|
+
const commands = sinon_1.default.spy(driver, 'executeCommand');
|
|
224
|
+
const before = yield idleClock();
|
|
225
|
+
const health = yield hubSide(sessionId).checkHealth();
|
|
226
|
+
(0, chai_1.expect)(health).to.deep.include({ isHealthy: true, errorType: XenonSession_1.HealthErrorType.NONE });
|
|
227
|
+
(0, chai_1.expect)(commands.callCount, 'commands the node’s driver ran').to.equal(0);
|
|
228
|
+
(0, chai_1.expect)(restarted.callCount, 'new-command timeouts restarted').to.equal(0);
|
|
229
|
+
(0, chai_1.expect)(yield idleClock(), 'the node’s idle clock').to.equal(before);
|
|
230
|
+
}));
|
|
231
|
+
it('reports a session the node no longer has as gone', () => __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
yield boot();
|
|
233
|
+
const sessionId = yield nodeSession();
|
|
234
|
+
yield umbrella.deleteSession(sessionId);
|
|
235
|
+
const health = yield hubSide(sessionId).checkHealth();
|
|
236
|
+
(0, chai_1.expect)(health).to.deep.include({
|
|
237
|
+
isHealthy: false,
|
|
238
|
+
errorType: XenonSession_1.HealthErrorType.SESSION_NOT_FOUND,
|
|
239
|
+
});
|
|
240
|
+
}));
|
|
241
|
+
it('the node answers only with the hub’s token for that session', () => __awaiter(this, void 0, void 0, function* () {
|
|
242
|
+
yield boot();
|
|
243
|
+
const sessionId = yield nodeSession();
|
|
244
|
+
const other = yield typedi_1.Container.get(hubSessionToken_1.HubSessionTokenIssuer).tokenFor('another-session');
|
|
245
|
+
const statusOf = (token) => {
|
|
246
|
+
const req = (0, loopbackRequest_1.default)(nodeOrigin).get(`/xenon/api/node/sessions/${sessionId}`);
|
|
247
|
+
return token ? req.set(hubSessionToken_1.HUB_TOKEN_HEADER, token) : req;
|
|
248
|
+
};
|
|
249
|
+
for (const res of [yield statusOf(), yield statusOf(other)]) {
|
|
250
|
+
(0, chai_1.expect)(res.status).to.equal(404);
|
|
251
|
+
(0, chai_1.expect)(res.body.value.error).to.equal('invalid session id');
|
|
252
|
+
(0, chai_1.expect)(res.headers[nodeSessionStatus_1.NODE_SESSION_STATUS_HEADER]).to.equal('1');
|
|
253
|
+
}
|
|
254
|
+
const own = yield typedi_1.Container.get(hubSessionToken_1.HubSessionTokenIssuer).tokenFor(sessionId);
|
|
255
|
+
const res = yield statusOf(own);
|
|
256
|
+
(0, chai_1.expect)(res.status).to.equal(200);
|
|
257
|
+
(0, chai_1.expect)(res.body.value).to.deep.equal({ sessionId, exists: true });
|
|
258
|
+
}));
|
|
259
|
+
it('asks no more than a command does: with per-command auth off, no token is needed', () => __awaiter(this, void 0, void 0, function* () {
|
|
260
|
+
yield boot();
|
|
261
|
+
const sessionId = yield nodeSession();
|
|
262
|
+
delete process.env.XENON_REQUIRE_COMMAND_AUTH;
|
|
263
|
+
const res = yield (0, loopbackRequest_1.default)(nodeOrigin).get(`/xenon/api/node/sessions/${sessionId}`);
|
|
264
|
+
(0, chai_1.expect)(res.status).to.equal(200);
|
|
265
|
+
(0, chai_1.expect)(res.body.value).to.deep.equal({ sessionId, exists: true });
|
|
266
|
+
}));
|
|
267
|
+
it('an older node without the route is probed the old way, and the hub says so once', () => __awaiter(this, void 0, void 0, function* () {
|
|
268
|
+
yield boot({ withRoute: false });
|
|
269
|
+
const sessionId = yield nodeSession();
|
|
270
|
+
const warn = sinon_1.default.stub(typedi_1.Container.get(nodeSessionStatus_1.NodeSessionProbeSupport).logger, 'warn');
|
|
271
|
+
const hub = hubSide(sessionId);
|
|
272
|
+
(0, chai_1.expect)(yield hub.checkHealth()).to.deep.include({ isHealthy: true });
|
|
273
|
+
(0, chai_1.expect)(yield hub.checkHealth()).to.deep.include({ isHealthy: true });
|
|
274
|
+
(0, chai_1.expect)(warn.callCount, 'warnings').to.equal(1);
|
|
275
|
+
(0, chai_1.expect)(String(warn.firstCall.args[0])).to.include(nodeOrigin);
|
|
276
|
+
}));
|
|
277
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
require("reflect-metadata");
|
|
16
|
+
const chai_1 = require("chai");
|
|
17
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
18
|
+
const express_1 = __importDefault(require("express"));
|
|
19
|
+
const typedi_1 = require("typedi");
|
|
20
|
+
const SessionLifecycleService_1 = require("../../src/services/SessionLifecycleService");
|
|
21
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
22
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
23
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
24
|
+
const prisma_store_1 = require("../../src/data-service/prisma-store");
|
|
25
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
26
|
+
const nodeWebDriverUrl_1 = require("../../src/gateway/nodeWebDriverUrl");
|
|
27
|
+
const sessionLocator_1 = require("../../src/gateway/sessionLocator");
|
|
28
|
+
const prisma_1 = require("../../src/prisma");
|
|
29
|
+
const SessionType_1 = __importDefault(require("../../src/enums/SessionType"));
|
|
30
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
31
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
32
|
+
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
33
|
+
/**
|
|
34
|
+
* A hub finds where a session runs after a restart from the session's row
|
|
35
|
+
* (SessionLocator, SessionManager.recoverActiveSessions). The row used to be
|
|
36
|
+
* written only with the dashboard on, and never for a cloud session, so a
|
|
37
|
+
* hub with the dashboard off lost every node session on restart: its
|
|
38
|
+
* commands went to the hub's own Appium, which had no such session, while the
|
|
39
|
+
* node kept it and the phone.
|
|
40
|
+
*
|
|
41
|
+
* A session the hub routes to a node or a cloud now always has its row. A
|
|
42
|
+
* local session's row is still the dashboard's to write.
|
|
43
|
+
*/
|
|
44
|
+
describe('a session the hub routes elsewhere survives a restart, dashboard on or off', () => {
|
|
45
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
46
|
+
const loopback = (0, loopbackServer_1.loopbackServers)();
|
|
47
|
+
const HUB_NODE_ID = 'hub-node-id';
|
|
48
|
+
let context;
|
|
49
|
+
let saved;
|
|
50
|
+
let restore;
|
|
51
|
+
let nodeOrigin;
|
|
52
|
+
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
+
const node = (0, express_1.default)();
|
|
54
|
+
node.get('/xenon/api/webdriver', (0, nodeWebDriverUrl_1.webdriverInfoHandler)(() => '/node'));
|
|
55
|
+
const server = yield loopback.serve(node);
|
|
56
|
+
nodeOrigin = `http://127.0.0.1:${server.address().port}`;
|
|
57
|
+
restore = (0, container_registration_1.saveRegistrations)(nodeWebDriverUrl_1.NodeBasePathResolver);
|
|
58
|
+
typedi_1.Container.set(nodeWebDriverUrl_1.NodeBasePathResolver, new nodeWebDriverUrl_1.NodeBasePathResolver());
|
|
59
|
+
context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
60
|
+
saved = {
|
|
61
|
+
context: Object.assign({}, context),
|
|
62
|
+
store: device_store_1.DeviceStoreFactory._deviceStore,
|
|
63
|
+
pending: device_store_1.DeviceStoreFactory._pendingSessionStore,
|
|
64
|
+
};
|
|
65
|
+
device_store_1.DeviceStoreFactory._deviceStore = new prisma_store_1.PrismaDeviceStore();
|
|
66
|
+
device_store_1.DeviceStoreFactory._pendingSessionStore = new prisma_store_1.PrismaPendingSessionStore();
|
|
67
|
+
context.pluginArgs = Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { enableDashboard: false });
|
|
68
|
+
context.nodeId = HUB_NODE_ID;
|
|
69
|
+
context.nodeBasePath = '/wd/hub';
|
|
70
|
+
yield scratch.db.session.deleteMany({});
|
|
71
|
+
yield scratch.db.device.deleteMany({});
|
|
72
|
+
}));
|
|
73
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
74
|
+
for (const s of SessionManager_1.SESSION_MANAGER.getAllSessions())
|
|
75
|
+
SessionManager_1.SESSION_MANAGER.removeSession(s.getId());
|
|
76
|
+
Object.assign(context, saved.context);
|
|
77
|
+
device_store_1.DeviceStoreFactory._deviceStore = saved.store;
|
|
78
|
+
device_store_1.DeviceStoreFactory._pendingSessionStore = saved.pending;
|
|
79
|
+
sinon_1.default.restore();
|
|
80
|
+
restore();
|
|
81
|
+
yield loopback.closeAll();
|
|
82
|
+
}));
|
|
83
|
+
function phone(udid, fields) {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
const row = yield scratch.db.device.create({
|
|
86
|
+
data: Object.assign({ udid, platform: 'android', name: udid, sdk: '14', busy: true, claimedAt: Date.now(), userBlocked: false, offline: false }, fields),
|
|
87
|
+
});
|
|
88
|
+
return row;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const caps = {
|
|
92
|
+
alwaysMatch: { platformName: 'Android', 'xe:options': { recordVideo: false } },
|
|
93
|
+
firstMatch: [{}],
|
|
94
|
+
};
|
|
95
|
+
/** The session is created on its phone as finalizeSession records it. */
|
|
96
|
+
function created(sessionId, device, remote) {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
yield new SessionLifecycleService_1.SessionLifecycleService().finalizeSession({ protocol: 'W3C', value: [sessionId, { platformName: 'Android' }, 'W3C'] }, device, caps, {}, remote, 'key-1', 'alice');
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** The hub restarts: nothing in memory survives. */
|
|
102
|
+
const restart = () => {
|
|
103
|
+
for (const s of SessionManager_1.SESSION_MANAGER.getAllSessions())
|
|
104
|
+
SessionManager_1.SESSION_MANAGER.removeSession(s.getId());
|
|
105
|
+
};
|
|
106
|
+
const locator = () => new sessionLocator_1.SessionLocator(Object.assign(Object.assign({ getSession: (id) => SessionManager_1.SESSION_MANAGER.getSession(id) }, (0, sessionLocator_1.sessionRowsFrom)(prisma_1.prisma)), { isLocalDevice: (d) => !d.cloud && d.nodeId === HUB_NODE_ID, webDriverUrlOf: (d) => __awaiter(void 0, void 0, void 0, function* () { return `${d.host}/node`; }) }));
|
|
107
|
+
it('a node’s session is routed to the node again, with the dashboard off', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
108
|
+
const device = yield phone('node-phone', { host: nodeOrigin, nodeId: 'node-peer' });
|
|
109
|
+
yield created('remote-1', device, true);
|
|
110
|
+
restart();
|
|
111
|
+
(0, chai_1.expect)(yield locator().locate('remote-1')).to.deep.include({
|
|
112
|
+
kind: 'remote',
|
|
113
|
+
url: `${nodeOrigin}/node`,
|
|
114
|
+
cloud: false,
|
|
115
|
+
});
|
|
116
|
+
}));
|
|
117
|
+
it('is rebuilt at boot with its owner, with the dashboard off', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
118
|
+
const device = yield phone('node-phone', { host: nodeOrigin, nodeId: 'node-peer' });
|
|
119
|
+
yield created('remote-1', device, true);
|
|
120
|
+
restart();
|
|
121
|
+
// SESSION_MANAGER itself: another spec may have replaced the container's
|
|
122
|
+
// SessionManager, and the gateway reads this one.
|
|
123
|
+
const recovered = yield SessionManager_1.SESSION_MANAGER.recoverActiveSessions('hub-after-restart', '/wd/hub');
|
|
124
|
+
(0, chai_1.expect)(recovered).to.equal(1);
|
|
125
|
+
const session = SessionManager_1.SESSION_MANAGER.getSession('remote-1');
|
|
126
|
+
(0, chai_1.expect)(session.getType()).to.equal(SessionType_1.default.REMOTE);
|
|
127
|
+
(0, chai_1.expect)(session.getWebDriverUrl()).to.equal(`${nodeOrigin}/node`);
|
|
128
|
+
(0, chai_1.expect)(session.userId).to.equal('alice');
|
|
129
|
+
(0, chai_1.expect)(session.apiKeyId).to.equal('key-1');
|
|
130
|
+
const row = yield scratch.db.session.findUnique({ where: { id: 'remote-1' } });
|
|
131
|
+
(0, chai_1.expect)(row).to.include({ user_id: 'alice', api_key_id: 'key-1', status: 'running' });
|
|
132
|
+
}));
|
|
133
|
+
it('a cloud session is routed to the cloud again', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
|
+
const device = yield phone('cloud-phone', {
|
|
135
|
+
host: 'https://hub.cloud.example/wd/hub',
|
|
136
|
+
// The store keeps the column as JSON (prisma-store.ts).
|
|
137
|
+
cloud: JSON.stringify('browserstack'),
|
|
138
|
+
});
|
|
139
|
+
yield created('cloud-1', Object.assign(Object.assign({}, device), { cloud: 'browserstack' }), true);
|
|
140
|
+
restart();
|
|
141
|
+
(0, chai_1.expect)(yield locator().locate('cloud-1')).to.deep.include({ kind: 'remote', cloud: true });
|
|
142
|
+
}));
|
|
143
|
+
it('a local session with the dashboard off still writes no row', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
144
|
+
const device = yield phone('hub-phone', {
|
|
145
|
+
host: 'http://127.0.0.1:4799',
|
|
146
|
+
nodeId: HUB_NODE_ID,
|
|
147
|
+
});
|
|
148
|
+
yield created('local-1', device, false);
|
|
149
|
+
(0, chai_1.expect)(yield scratch.db.session.count()).to.equal(0);
|
|
150
|
+
}));
|
|
151
|
+
});
|