@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
|
@@ -49,7 +49,7 @@ require("reflect-metadata");
|
|
|
49
49
|
const chai_1 = require("chai");
|
|
50
50
|
const sinon_1 = __importDefault(require("sinon"));
|
|
51
51
|
const express_1 = __importDefault(require("express"));
|
|
52
|
-
const
|
|
52
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
53
53
|
const typedi_1 = require("typedi");
|
|
54
54
|
const control_1 = __importStar(require("../../src/app/routers/control"));
|
|
55
55
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
@@ -60,6 +60,9 @@ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/io
|
|
|
60
60
|
const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
|
|
61
61
|
const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
|
|
62
62
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
63
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
64
|
+
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
65
|
+
const own_node_id_1 = require("../helpers/own-node-id");
|
|
63
66
|
const UDID = 'DEV-1';
|
|
64
67
|
const ALICE = 'usr_alice';
|
|
65
68
|
const BOB = 'usr_bob';
|
|
@@ -82,6 +85,8 @@ function buildApp(userId) {
|
|
|
82
85
|
return app;
|
|
83
86
|
}
|
|
84
87
|
describe('POST /control/:udid/stream/leave', () => {
|
|
88
|
+
// The fake rows are this server's own phones.
|
|
89
|
+
(0, own_node_id_1.useOwnNodeId)();
|
|
85
90
|
let deviceRow;
|
|
86
91
|
let unblock;
|
|
87
92
|
let androidStop;
|
|
@@ -90,10 +95,16 @@ describe('POST /control/:udid/stream/leave', () => {
|
|
|
90
95
|
let h264Clients;
|
|
91
96
|
let mjpegViewers;
|
|
92
97
|
let iosViewers;
|
|
98
|
+
let restoreContainer;
|
|
99
|
+
// Served on 127.0.0.1: request(app) listens on every address and connects
|
|
100
|
+
// to 127.0.0.1, where another process's listener on that port can answer.
|
|
101
|
+
const loopback = (0, loopbackServer_1.loopbackServers)();
|
|
93
102
|
beforeEach(() => {
|
|
103
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, AndroidStreamService_1.default, AndroidH264StreamService_1.default, IOSStreamService_1.default, recording_store_1.RecordingStore);
|
|
94
104
|
deviceRow = {
|
|
95
105
|
udid: UDID,
|
|
96
106
|
host: '127.0.0.1',
|
|
107
|
+
nodeId: own_node_id_1.OWN_NODE_ID,
|
|
97
108
|
platform: 'android',
|
|
98
109
|
busy: true,
|
|
99
110
|
session_id: `manual_${ALICE}_${UDID}`,
|
|
@@ -126,14 +137,15 @@ describe('POST /control/:udid/stream/leave', () => {
|
|
|
126
137
|
isRecording: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
127
138
|
});
|
|
128
139
|
});
|
|
129
|
-
afterEach(() => {
|
|
140
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
130
141
|
sinon_1.default.restore();
|
|
131
|
-
|
|
132
|
-
|
|
142
|
+
restoreContainer();
|
|
143
|
+
yield loopback.closeAll();
|
|
144
|
+
}));
|
|
133
145
|
describe('the route', () => {
|
|
134
146
|
it('schedules the leave for the holder and answers at once', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
147
|
const leave = sinon_1.default.stub(control_1.previewLeaves, 'leave');
|
|
136
|
-
const res = yield (0,
|
|
148
|
+
const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(ALICE))).post(`/xenon/api/control/${UDID}/stream/leave`);
|
|
137
149
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(202);
|
|
138
150
|
(0, chai_1.expect)(leave.calledOnceWith(UDID)).to.equal(true);
|
|
139
151
|
// Nothing is stopped yet: that waits for the grace and the viewer count.
|
|
@@ -141,14 +153,14 @@ describe('POST /control/:udid/stream/leave', () => {
|
|
|
141
153
|
}));
|
|
142
154
|
it('refuses someone else’s hold', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
143
155
|
const leave = sinon_1.default.stub(control_1.previewLeaves, 'leave');
|
|
144
|
-
const res = yield (0,
|
|
156
|
+
const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(BOB))).post(`/xenon/api/control/${UDID}/stream/leave`);
|
|
145
157
|
(0, chai_1.expect)(res.status).to.equal(403);
|
|
146
158
|
(0, chai_1.expect)(leave.called).to.equal(false);
|
|
147
159
|
}));
|
|
148
160
|
// A reload leaves and then starts again; the start must win.
|
|
149
161
|
it('stream/start cancels a pending leave', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
150
162
|
const cancel = sinon_1.default.stub(control_1.previewLeaves, 'cancel');
|
|
151
|
-
yield (0,
|
|
163
|
+
yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(ALICE))).post(`/xenon/api/control/${UDID}/stream/start`);
|
|
152
164
|
(0, chai_1.expect)(cancel.calledWith(UDID)).to.equal(true);
|
|
153
165
|
}));
|
|
154
166
|
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
require("reflect-metadata");
|
|
49
|
+
const chai_1 = require("chai");
|
|
50
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
51
|
+
const typedi_1 = require("typedi");
|
|
52
|
+
const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
|
|
53
|
+
const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
|
|
54
|
+
const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
|
|
55
|
+
const previewHold_1 = require("../../src/device-managers/android/previewHold");
|
|
56
|
+
const singleFlight_1 = require("../../src/helpers/singleFlight");
|
|
57
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
58
|
+
const deviceService = __importStar(require("../../src/data-service/device-service"));
|
|
59
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
60
|
+
const PortAllocator_1 = require("../../src/services/PortAllocator");
|
|
61
|
+
const recording_store_1 = require("../../src/services/recording/recording-store");
|
|
62
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
63
|
+
/**
|
|
64
|
+
* A hub's table holds its nodes' rows too, and a node on the same machine
|
|
65
|
+
* sees the same phones (an iPhone, emulator-5554). The stream services
|
|
66
|
+
* looked a phone up by udid alone, so on such a hub they could act on the
|
|
67
|
+
* node's row: release the node's preview hold, or judge "an Appium session
|
|
68
|
+
* holds this phone" from the node's session. A phone's own row is the one
|
|
69
|
+
* filed under a host this server's discovery writes (localDeviceHosts).
|
|
70
|
+
*
|
|
71
|
+
* The store fake matches every key of the filter, as Prisma's findFirst does.
|
|
72
|
+
*/
|
|
73
|
+
const IP = '192.168.0.104';
|
|
74
|
+
const HUB = `http://${IP}:4724`;
|
|
75
|
+
const NODE = `http://${IP}:4725`;
|
|
76
|
+
const UDID = 'shared-phone-1';
|
|
77
|
+
/** A live-preview hold on the phone, as formatManualLock writes it. */
|
|
78
|
+
const hold = (userId) => `manual_${userId}_${UDID}`;
|
|
79
|
+
describe("stream services act on this server's own row for a udid", () => {
|
|
80
|
+
let rows;
|
|
81
|
+
let unblock;
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
rows = [];
|
|
84
|
+
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
|
|
85
|
+
findDevice: (filter) => __awaiter(void 0, void 0, void 0, function* () {
|
|
86
|
+
var _a;
|
|
87
|
+
return ((_a = rows.find((r) => Object.entries(filter).every(([k, v]) => r[k] === v))) !== null && _a !== void 0 ? _a : null);
|
|
88
|
+
}),
|
|
89
|
+
updateDevice: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }),
|
|
90
|
+
});
|
|
91
|
+
const hub = new PluginContext_1.PluginContext();
|
|
92
|
+
hub.setContext(Object.assign({}, IPluginArgs_1.DefaultPluginArgs, { bindHostOrIp: IP }), 4724, 'hub', '');
|
|
93
|
+
const real = typedi_1.Container.get.bind(typedi_1.Container);
|
|
94
|
+
sinon_1.default.stub(typedi_1.Container, 'get').callsFake((token) => {
|
|
95
|
+
if (token === PluginContext_1.PluginContext)
|
|
96
|
+
return hub;
|
|
97
|
+
if (token === PortAllocator_1.PortAllocator)
|
|
98
|
+
return { release: () => __awaiter(void 0, void 0, void 0, function* () { return undefined; }) };
|
|
99
|
+
if (token === recording_store_1.RecordingStore)
|
|
100
|
+
return { isRecording: () => __awaiter(void 0, void 0, void 0, function* () { return false; }) };
|
|
101
|
+
if (token === AndroidH264StreamService_1.default)
|
|
102
|
+
return { getMultiplexer: () => undefined };
|
|
103
|
+
if (token === AndroidStreamService_1.default)
|
|
104
|
+
return { getStreamStatus: () => undefined };
|
|
105
|
+
return real(token);
|
|
106
|
+
});
|
|
107
|
+
unblock = sinon_1.default.stub(deviceService, 'unblockDevice').resolves();
|
|
108
|
+
sinon_1.default.stub(process, 'kill');
|
|
109
|
+
});
|
|
110
|
+
afterEach(() => sinon_1.default.restore());
|
|
111
|
+
function iosService() {
|
|
112
|
+
// Bypass the constructor: it starts watchdog intervals.
|
|
113
|
+
const svc = Object.create(IOSStreamService_1.default.prototype);
|
|
114
|
+
svc.sessions = new Map();
|
|
115
|
+
svc.startFlight = new singleFlight_1.SingleFlight();
|
|
116
|
+
svc.recoveryCooldowns = new Map();
|
|
117
|
+
svc.cleanupOrphanTunnels = () => __awaiter(this, void 0, void 0, function* () { return undefined; });
|
|
118
|
+
return svc;
|
|
119
|
+
}
|
|
120
|
+
it("iOS: stopping the hub's stream never releases the node's preview hold", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
121
|
+
rows = [
|
|
122
|
+
{ udid: UDID, host: NODE, platform: 'ios', busy: true, session_id: `manual_usr_bob_${UDID}` },
|
|
123
|
+
{ udid: UDID, host: HUB, platform: 'ios', busy: false, session_id: undefined },
|
|
124
|
+
];
|
|
125
|
+
const svc = iosService();
|
|
126
|
+
svc.sessions.set(UDID, {
|
|
127
|
+
udid: UDID,
|
|
128
|
+
wdaProcess: null,
|
|
129
|
+
forwardWDAProcess: null,
|
|
130
|
+
forwardMJPEGProcess: null,
|
|
131
|
+
tunnelProcess: null,
|
|
132
|
+
wdaPort: 28101,
|
|
133
|
+
mjpegPort: 29101,
|
|
134
|
+
status: 'running',
|
|
135
|
+
lastViewerAt: Date.now(),
|
|
136
|
+
viewerCount: 0,
|
|
137
|
+
});
|
|
138
|
+
yield svc.stopStream(UDID);
|
|
139
|
+
(0, chai_1.expect)(unblock.called, `unblocked ${JSON.stringify(unblock.args)}`).to.equal(false);
|
|
140
|
+
}));
|
|
141
|
+
it("iOS: whether an Appium session holds the phone is read from the hub's own row", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
142
|
+
rows = [
|
|
143
|
+
{ udid: UDID, host: NODE, platform: 'ios', busy: false, session_id: undefined },
|
|
144
|
+
{ udid: UDID, host: HUB, platform: 'ios', busy: true, session_id: 'hub-appium-session' },
|
|
145
|
+
];
|
|
146
|
+
(0, chai_1.expect)(yield iosService().appiumSessionMayUse(UDID, 'go-ios tunnels')).to.equal(true);
|
|
147
|
+
}));
|
|
148
|
+
it("Android: a stopping preview never releases the node's preview hold", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
149
|
+
rows = [
|
|
150
|
+
{ udid: UDID, host: NODE, platform: 'android', busy: true, session_id: hold('usr_bob') },
|
|
151
|
+
{ udid: UDID, host: HUB, platform: 'android', busy: false, session_id: undefined },
|
|
152
|
+
];
|
|
153
|
+
(0, chai_1.expect)(yield (0, previewHold_1.releaseIdlePreviewHold)(UDID)).to.equal(false);
|
|
154
|
+
(0, chai_1.expect)(unblock.called).to.equal(false);
|
|
155
|
+
}));
|
|
156
|
+
it("Android: the hub's own preview hold is still released", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
|
+
rows = [
|
|
158
|
+
{ udid: UDID, host: NODE, platform: 'android', busy: false, session_id: undefined },
|
|
159
|
+
{ udid: UDID, host: HUB, platform: 'android', busy: true, session_id: hold('usr_al') },
|
|
160
|
+
];
|
|
161
|
+
(0, chai_1.expect)(yield (0, previewHold_1.releaseIdlePreviewHold)(UDID)).to.equal(true);
|
|
162
|
+
(0, chai_1.expect)(unblock.calledOnceWith(UDID, HUB)).to.equal(true);
|
|
163
|
+
}));
|
|
164
|
+
});
|
|
@@ -49,7 +49,7 @@ require("reflect-metadata");
|
|
|
49
49
|
const chai_1 = require("chai");
|
|
50
50
|
const sinon_1 = __importDefault(require("sinon"));
|
|
51
51
|
const express_1 = __importDefault(require("express"));
|
|
52
|
-
const
|
|
52
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
53
53
|
const typedi_1 = require("typedi");
|
|
54
54
|
const control_1 = __importDefault(require("../../src/app/routers/control"));
|
|
55
55
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
@@ -60,6 +60,9 @@ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/io
|
|
|
60
60
|
const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
|
|
61
61
|
const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
|
|
62
62
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
63
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
64
|
+
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
65
|
+
const own_node_id_1 = require("../helpers/own-node-id");
|
|
63
66
|
/**
|
|
64
67
|
* Round-trip guard for POST /control/:udid/stream/stop.
|
|
65
68
|
*
|
|
@@ -117,20 +120,28 @@ function buildApp(caller) {
|
|
|
117
120
|
return app;
|
|
118
121
|
}
|
|
119
122
|
describe('POST /control/:udid/stream/stop — lock ownership round-trip', () => {
|
|
123
|
+
// The fake rows are this server's own phones.
|
|
124
|
+
(0, own_node_id_1.useOwnNodeId)();
|
|
120
125
|
let unblock;
|
|
121
126
|
let androidStop;
|
|
122
127
|
let h264Stop;
|
|
123
128
|
let deviceRow;
|
|
129
|
+
let restoreContainer;
|
|
130
|
+
// Served on 127.0.0.1: request(app) listens on every address and connects
|
|
131
|
+
// to 127.0.0.1, where another process's listener on that port can answer.
|
|
132
|
+
const loopback = (0, loopbackServer_1.loopbackServers)();
|
|
124
133
|
function setLock(sessionId) {
|
|
125
134
|
deviceRow = {
|
|
126
135
|
udid: UDID,
|
|
127
136
|
host: '127.0.0.1',
|
|
137
|
+
nodeId: own_node_id_1.OWN_NODE_ID,
|
|
128
138
|
platform: 'android',
|
|
129
139
|
busy: sessionId !== null,
|
|
130
140
|
session_id: sessionId,
|
|
131
141
|
};
|
|
132
142
|
}
|
|
133
143
|
beforeEach(() => {
|
|
144
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, AndroidStreamService_1.default, AndroidH264StreamService_1.default, IOSStreamService_1.default, recording_store_1.RecordingStore);
|
|
134
145
|
setLock(`manual_${ALICE}_${UDID}`);
|
|
135
146
|
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
|
|
136
147
|
findDevice: () => __awaiter(void 0, void 0, void 0, function* () { return deviceRow; }),
|
|
@@ -145,11 +156,12 @@ describe('POST /control/:udid/stream/stop — lock ownership round-trip', () =>
|
|
|
145
156
|
// Nothing is being recorded here (see stream-stop-recording.spec.ts).
|
|
146
157
|
typedi_1.Container.set(recording_store_1.RecordingStore, { activeRecordingFor: () => __awaiter(void 0, void 0, void 0, function* () { return null; }) });
|
|
147
158
|
});
|
|
148
|
-
afterEach(() => {
|
|
159
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
149
160
|
sinon_1.default.restore();
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
restoreContainer();
|
|
162
|
+
yield loopback.closeAll();
|
|
163
|
+
}));
|
|
164
|
+
const stop = (caller) => __awaiter(void 0, void 0, void 0, function* () { return (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(caller))).post(`/xenon/api/control/${UDID}/stream/stop`); });
|
|
153
165
|
it('CRITICAL: the header-pair caller who started the stream can stop it', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
154
166
|
// Exactly what stream/start wrote for this caller: manual_<userId>_<udid>.
|
|
155
167
|
// req.apiKey.id is set and DIFFERENT — the pre-fix reader picked it and 403'd.
|
|
@@ -49,7 +49,7 @@ require("reflect-metadata");
|
|
|
49
49
|
const chai_1 = require("chai");
|
|
50
50
|
const sinon_1 = __importDefault(require("sinon"));
|
|
51
51
|
const express_1 = __importDefault(require("express"));
|
|
52
|
-
const
|
|
52
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
53
53
|
const typedi_1 = require("typedi");
|
|
54
54
|
const control_1 = __importDefault(require("../../src/app/routers/control"));
|
|
55
55
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
@@ -60,6 +60,9 @@ const IOSStreamService_1 = __importDefault(require("../../src/device-managers/io
|
|
|
60
60
|
const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
|
|
61
61
|
const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
|
|
62
62
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
63
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
64
|
+
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
65
|
+
const own_node_id_1 = require("../helpers/own-node-id");
|
|
63
66
|
/**
|
|
64
67
|
* POST /control/:udid/stream/stop must not stop the stream under a live
|
|
65
68
|
* recording. The recording reads from that stream: measured 2026-09-26 on a
|
|
@@ -92,17 +95,25 @@ function buildApp(caller) {
|
|
|
92
95
|
return app;
|
|
93
96
|
}
|
|
94
97
|
describe('POST /control/:udid/stream/stop — a device being recorded', () => {
|
|
98
|
+
// The fake rows are this server's own phones.
|
|
99
|
+
(0, own_node_id_1.useOwnNodeId)();
|
|
95
100
|
let unblock;
|
|
96
101
|
let androidStop;
|
|
97
102
|
let h264Stop;
|
|
98
103
|
let active;
|
|
104
|
+
let restoreContainer;
|
|
105
|
+
// Served on 127.0.0.1: request(app) listens on every address and connects
|
|
106
|
+
// to 127.0.0.1, where another process's listener on that port can answer.
|
|
107
|
+
const loopback = (0, loopbackServer_1.loopbackServers)();
|
|
99
108
|
beforeEach(() => {
|
|
109
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, AndroidStreamService_1.default, AndroidH264StreamService_1.default, IOSStreamService_1.default, recording_store_1.RecordingStore);
|
|
100
110
|
active = { id: 'rec-1', groupId: 'grp-1' };
|
|
101
111
|
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
|
|
102
112
|
findDevice: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
113
|
return ({
|
|
104
114
|
udid: UDID,
|
|
105
115
|
host: '127.0.0.1',
|
|
116
|
+
nodeId: own_node_id_1.OWN_NODE_ID,
|
|
106
117
|
platform: 'android',
|
|
107
118
|
busy: true,
|
|
108
119
|
session_id: `manual_${ALICE}_${UDID}`,
|
|
@@ -118,11 +129,12 @@ describe('POST /control/:udid/stream/stop — a device being recorded', () => {
|
|
|
118
129
|
typedi_1.Container.set(IOSStreamService_1.default, { stopStream: sinon_1.default.stub().resolves() });
|
|
119
130
|
typedi_1.Container.set(recording_store_1.RecordingStore, { activeRecordingFor: () => __awaiter(void 0, void 0, void 0, function* () { return active; }) });
|
|
120
131
|
});
|
|
121
|
-
afterEach(() => {
|
|
132
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
122
133
|
sinon_1.default.restore();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
restoreContainer();
|
|
135
|
+
yield loopback.closeAll();
|
|
136
|
+
}));
|
|
137
|
+
const stop = (caller) => __awaiter(void 0, void 0, void 0, function* () { return (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(caller))).post(`/xenon/api/control/${UDID}/stream/stop`); });
|
|
126
138
|
it('refuses the owner with 409 and names the recording to stop instead', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
127
139
|
const res = yield stop({ userId: ALICE });
|
|
128
140
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(409);
|
|
@@ -16,7 +16,7 @@ require("reflect-metadata");
|
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const sinon_1 = __importDefault(require("sinon"));
|
|
18
18
|
const express_1 = __importDefault(require("express"));
|
|
19
|
-
const
|
|
19
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
20
20
|
const typedi_1 = require("typedi");
|
|
21
21
|
const fs_1 = __importDefault(require("fs"));
|
|
22
22
|
const os_1 = __importDefault(require("os"));
|
|
@@ -29,6 +29,9 @@ const UserService_1 = require("../../src/services/UserService");
|
|
|
29
29
|
const ApiKeyService_1 = require("../../src/services/ApiKeyService");
|
|
30
30
|
const UserSessionService_1 = require("../../src/services/UserSessionService");
|
|
31
31
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
32
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
33
|
+
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
34
|
+
const own_node_id_1 = require("../helpers/own-node-id");
|
|
32
35
|
/**
|
|
33
36
|
* `req.auth.userId` must always hold a User id.
|
|
34
37
|
*
|
|
@@ -49,6 +52,8 @@ const UDID = 'UDID-1';
|
|
|
49
52
|
const ALICE_USER = 'usr_alice';
|
|
50
53
|
const ALICE_KEY = 'key_abc';
|
|
51
54
|
describe('stream/ticket carries the user identity, not the credential', () => {
|
|
55
|
+
// The fake rows are this server's own phones.
|
|
56
|
+
(0, own_node_id_1.useOwnNodeId)();
|
|
52
57
|
let dir;
|
|
53
58
|
let ticketSvc;
|
|
54
59
|
function buildApp(caller = {}) {
|
|
@@ -78,7 +83,12 @@ describe('stream/ticket carries the user identity, not the credential', () => {
|
|
|
78
83
|
app.use('/xenon/api', apiRouter);
|
|
79
84
|
return app;
|
|
80
85
|
}
|
|
86
|
+
let restoreContainer;
|
|
87
|
+
// Served on 127.0.0.1: request(app) listens on every address and connects
|
|
88
|
+
// to 127.0.0.1, where another process's listener on that port can answer.
|
|
89
|
+
const loopback = (0, loopbackServer_1.loopbackServers)();
|
|
81
90
|
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService, StreamTicketService_1.StreamTicketService, UserService_1.UserService, ApiKeyService_1.ApiKeyService, UserSessionService_1.UserSessionService);
|
|
82
92
|
dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-ticket-identity-'));
|
|
83
93
|
const keySvc = new JwtKeyService_1.JwtKeyService();
|
|
84
94
|
yield keySvc.init(dir);
|
|
@@ -96,17 +106,27 @@ describe('stream/ticket carries the user identity, not the credential', () => {
|
|
|
96
106
|
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
|
|
97
107
|
findDevice: (_a) => __awaiter(void 0, [_a], void 0, function* ({ udid }) {
|
|
98
108
|
return udid === UDID
|
|
99
|
-
? {
|
|
109
|
+
? {
|
|
110
|
+
udid,
|
|
111
|
+
host: 'h',
|
|
112
|
+
nodeId: own_node_id_1.OWN_NODE_ID,
|
|
113
|
+
platform: 'android',
|
|
114
|
+
screenWidth: '1080',
|
|
115
|
+
screenHeight: '1920',
|
|
116
|
+
}
|
|
100
117
|
: null;
|
|
101
118
|
}),
|
|
102
119
|
});
|
|
103
120
|
}));
|
|
104
|
-
afterEach(() => {
|
|
121
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
105
122
|
sinon_1.default.restore();
|
|
106
|
-
|
|
123
|
+
restoreContainer();
|
|
124
|
+
yield loopback.closeAll();
|
|
107
125
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
126
|
+
}));
|
|
127
|
+
const mint = (caller) => __awaiter(void 0, void 0, void 0, function* () {
|
|
128
|
+
return (0, loopbackRequest_1.default)(yield loopback.serve(buildApp(caller))).post(`/xenon/api/control/${UDID}/stream/ticket`);
|
|
108
129
|
});
|
|
109
|
-
const mint = (caller) => (0, supertest_1.default)(buildApp(caller)).post(`/xenon/api/control/${UDID}/stream/ticket`);
|
|
110
130
|
it('mints with the userId even when a different apiKey id is present', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
111
131
|
const res = yield mint({ userId: ALICE_USER, apiKeyId: ALICE_KEY });
|
|
112
132
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
@@ -150,7 +170,7 @@ describe('stream/ticket carries the user identity, not the credential', () => {
|
|
|
150
170
|
// two must match: minting for any udid would mark the ones that 404 as
|
|
151
171
|
// phones another team owns.
|
|
152
172
|
it("404s a udid that isn't a device, and mints nothing", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
153
|
-
const res = yield (0,
|
|
173
|
+
const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(buildApp({ userId: ALICE_USER }))).post('/xenon/api/control/NO-SUCH-DEVICE/stream/ticket');
|
|
154
174
|
(0, chai_1.expect)(res.status).to.equal(404);
|
|
155
175
|
(0, chai_1.expect)(res.text).to.equal('Device not found');
|
|
156
176
|
}));
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
require("reflect-metadata");
|
|
7
|
+
const chai_1 = require("chai");
|
|
8
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
9
|
+
const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
|
|
10
|
+
const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
|
|
11
|
+
const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
|
|
12
|
+
/**
|
|
13
|
+
* The stream services start their watchdogs in their constructors. On a
|
|
14
|
+
* server the HTTP listener keeps the process alive, so those intervals only
|
|
15
|
+
* need to fire while it runs; referenced, they also kept a test run (or any
|
|
16
|
+
* script that merely constructs a service) alive after its work was done.
|
|
17
|
+
*/
|
|
18
|
+
describe('stream service watchdogs', () => {
|
|
19
|
+
let created;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
created = [];
|
|
22
|
+
const realSetInterval = global.setInterval;
|
|
23
|
+
sinon_1.default.stub(global, 'setInterval').callsFake(((fn, ms) => {
|
|
24
|
+
const timer = realSetInterval(fn, ms);
|
|
25
|
+
created.push(timer);
|
|
26
|
+
return timer;
|
|
27
|
+
}));
|
|
28
|
+
});
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
sinon_1.default.restore();
|
|
31
|
+
for (const timer of created)
|
|
32
|
+
clearInterval(timer);
|
|
33
|
+
});
|
|
34
|
+
for (const [name, Service] of [
|
|
35
|
+
['AndroidStreamService', AndroidStreamService_1.default],
|
|
36
|
+
['AndroidH264StreamService', AndroidH264StreamService_1.default],
|
|
37
|
+
['IOSStreamService', IOSStreamService_1.default],
|
|
38
|
+
]) {
|
|
39
|
+
it(`${name}'s intervals don't keep the process alive`, () => {
|
|
40
|
+
new Service();
|
|
41
|
+
(0, chai_1.expect)(created.length, 'the constructor starts its watchdog').to.be.greaterThan(0);
|
|
42
|
+
for (const timer of created)
|
|
43
|
+
(0, chai_1.expect)(timer.hasRef(), 'unref()ed').to.equal(false);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|