@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,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 net_1 = __importDefault(require("net"));
|
|
18
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
19
|
+
const PortAllocator_1 = require("../../src/services/PortAllocator");
|
|
20
|
+
const scratch_port_leases_1 = require("../helpers/scratch-port-leases");
|
|
21
|
+
const SHAPES = [
|
|
22
|
+
{ host: '127.0.0.1', label: 'IPv4 loopback (127.0.0.1)' },
|
|
23
|
+
{ host: '::1', label: 'IPv6 loopback (::1)' },
|
|
24
|
+
{ host: '0.0.0.0', label: 'IPv4 wildcard (0.0.0.0)' },
|
|
25
|
+
{ host: '::', label: 'IPv6 wildcard, dual-stack (::) — what iproxy does' },
|
|
26
|
+
{ host: '::', ipv6Only: true, label: 'IPv6 wildcard, v6-only (::)' },
|
|
27
|
+
];
|
|
28
|
+
function listen(port, host, ipv6Only = false) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const server = net_1.default.createServer((socket) => socket.destroy());
|
|
31
|
+
server.once('error', reject);
|
|
32
|
+
server.listen({ port, host, ipv6Only }, () => resolve(server));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function close(server) {
|
|
36
|
+
return new Promise((resolve) => server.close(() => resolve()));
|
|
37
|
+
}
|
|
38
|
+
describe('PortAllocator OS probe and lease ownership (real sockets, scratch DB)', () => {
|
|
39
|
+
const scratch = (0, scratch_port_leases_1.useScratchPortLeases)();
|
|
40
|
+
let allocator;
|
|
41
|
+
let servers;
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
allocator = new PortAllocator_1.PortAllocator();
|
|
44
|
+
allocator.configure({
|
|
45
|
+
mjpeg: [scratch.base, scratch.base + 1],
|
|
46
|
+
wda: [scratch.base + 50, scratch.base + 51],
|
|
47
|
+
});
|
|
48
|
+
servers = [];
|
|
49
|
+
});
|
|
50
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
51
|
+
yield Promise.all(servers.map(close));
|
|
52
|
+
sinon_1.default.restore();
|
|
53
|
+
}));
|
|
54
|
+
function occupy(port, shape) {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
servers.push(yield listen(port, shape.host, shape.ipv6Only));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function leaseOf(port) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
return scratch.db.portLease.findUnique({ where: { port } });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
describe('acquire skips a port something is listening on', () => {
|
|
65
|
+
for (const shape of SHAPES) {
|
|
66
|
+
it(`a listener on ${shape.label}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
var _a;
|
|
68
|
+
yield occupy(scratch.base, shape);
|
|
69
|
+
const port = yield allocator.acquire('mjpeg', 'android-s9');
|
|
70
|
+
(0, chai_1.expect)(port, `must not lease a port with a listener on ${shape.host}`).to.equal(scratch.base + 1);
|
|
71
|
+
(0, chai_1.expect)(yield leaseOf(scratch.base)).to.equal(null);
|
|
72
|
+
(0, chai_1.expect)((_a = (yield leaseOf(scratch.base + 1))) === null || _a === void 0 ? void 0 : _a.leasedToUdid).to.equal('android-s9');
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
it("drops the udid's own stale lease when an iproxy-shaped listener holds the port", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
|
+
// The reuse path: the S9+ still has a lease row on the port while the
|
|
77
|
+
// iPhone's iproxy is the one listening there.
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
yield scratch.db.portLease.create({
|
|
80
|
+
data: {
|
|
81
|
+
port: scratch.base,
|
|
82
|
+
purpose: 'mjpeg',
|
|
83
|
+
leasedToUdid: 'android-s9',
|
|
84
|
+
leasedAt: now,
|
|
85
|
+
expiresAt: now + 60000,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
yield occupy(scratch.base, SHAPES[3]);
|
|
89
|
+
const port = yield allocator.acquire('mjpeg', 'android-s9');
|
|
90
|
+
(0, chai_1.expect)(port).to.equal(scratch.base + 1);
|
|
91
|
+
(0, chai_1.expect)(yield leaseOf(scratch.base)).to.equal(null);
|
|
92
|
+
}));
|
|
93
|
+
});
|
|
94
|
+
it('still leases a port nothing is listening on', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
|
+
const port = yield allocator.acquire('mjpeg', 'android-s9');
|
|
96
|
+
(0, chai_1.expect)(port).to.equal(scratch.base);
|
|
97
|
+
const row = yield leaseOf(scratch.base);
|
|
98
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.leasedToUdid).to.equal('android-s9');
|
|
99
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.purpose).to.equal('mjpeg');
|
|
100
|
+
}));
|
|
101
|
+
describe('a host without IPv6', () => {
|
|
102
|
+
it('does not count an IPv6 address it cannot bind as "in use"', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
|
+
const probe = sinon_1.default.stub(allocator, 'bindProbe').callsFake((...args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
104
|
+
const host = args[1];
|
|
105
|
+
if (host === '::')
|
|
106
|
+
return 'EAFNOSUPPORT';
|
|
107
|
+
if (host === '::1')
|
|
108
|
+
return 'EADDRNOTAVAIL';
|
|
109
|
+
return null;
|
|
110
|
+
}));
|
|
111
|
+
(0, chai_1.expect)(yield allocator.acquire('mjpeg', 'android-s9')).to.equal(scratch.base);
|
|
112
|
+
const hosts = probe.getCalls().map((c) => c.args[1]);
|
|
113
|
+
(0, chai_1.expect)(hosts).to.include.members(['127.0.0.1', '0.0.0.0', '::1', '::']);
|
|
114
|
+
}));
|
|
115
|
+
it('still counts EADDRINUSE on an IPv6 address as in use', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
sinon_1.default.stub(allocator, 'bindProbe').callsFake((...args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
|
+
const [port, host] = args;
|
|
118
|
+
return host === '::' && port === scratch.base ? 'EADDRINUSE' : null;
|
|
119
|
+
}));
|
|
120
|
+
(0, chai_1.expect)(yield allocator.acquire('mjpeg', 'android-s9')).to.equal(scratch.base + 1);
|
|
121
|
+
}));
|
|
122
|
+
});
|
|
123
|
+
describe('release is scoped to the lease holder', () => {
|
|
124
|
+
it("leaves another udid's lease on the same port alone", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
125
|
+
var _a;
|
|
126
|
+
const port = yield allocator.acquire('mjpeg', 'android-s9');
|
|
127
|
+
yield allocator.release(port, 'iphone-17');
|
|
128
|
+
(0, chai_1.expect)((_a = (yield leaseOf(port))) === null || _a === void 0 ? void 0 : _a.leasedToUdid, 'a stale owner must not free a live lease').to.equal('android-s9');
|
|
129
|
+
}));
|
|
130
|
+
it('deletes the lease when the holder releases it', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
131
|
+
const port = yield allocator.acquire('mjpeg', 'android-s9');
|
|
132
|
+
yield allocator.release(port, 'android-s9');
|
|
133
|
+
(0, chai_1.expect)(yield leaseOf(port)).to.equal(null);
|
|
134
|
+
}));
|
|
135
|
+
});
|
|
136
|
+
describe('claim: leasing a port the udid is already serving on', () => {
|
|
137
|
+
it('leases a port with a live listener, which acquire would refuse', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
138
|
+
yield occupy(scratch.base + 50, SHAPES[3]);
|
|
139
|
+
(0, chai_1.expect)(yield allocator.claim('wda', 'iphone-17', scratch.base + 50)).to.equal(true);
|
|
140
|
+
const row = yield leaseOf(scratch.base + 50);
|
|
141
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.leasedToUdid).to.equal('iphone-17');
|
|
142
|
+
(0, chai_1.expect)(row === null || row === void 0 ? void 0 : row.purpose).to.equal('wda');
|
|
143
|
+
}));
|
|
144
|
+
it("refuses, and keeps the row, when another udid holds the port's lease", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
145
|
+
var _a;
|
|
146
|
+
const held = yield allocator.acquire('wda', 'iphone-other');
|
|
147
|
+
(0, chai_1.expect)(yield allocator.claim('wda', 'iphone-17', held)).to.equal(false);
|
|
148
|
+
(0, chai_1.expect)((_a = (yield leaseOf(held))) === null || _a === void 0 ? void 0 : _a.leasedToUdid).to.equal('iphone-other');
|
|
149
|
+
}));
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -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 profile_1 = require("../../src/app/routers/profile");
|
|
21
21
|
const typedi_1 = require("typedi");
|
|
22
22
|
const ApiKeyService_1 = require("../../src/services/ApiKeyService");
|
|
@@ -34,13 +34,13 @@ describe('profile router', () => {
|
|
|
34
34
|
it('GET /profile/access-key returns the caller accessKey', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
35
|
sinon_1.default.stub(typedi_1.Container.get(UserService_1.UserService), 'findById').resolves({ accessKey: 'xen_abc' });
|
|
36
36
|
const app = appWithAuth({ userId: 'u1', role: 'ADMIN', scopes: 'devices,sessions,read' });
|
|
37
|
-
const r = yield (0,
|
|
37
|
+
const r = yield (0, loopbackRequest_1.default)(app).get('/profile/access-key');
|
|
38
38
|
(0, chai_1.expect)(r.body.accessKey).to.equal('xen_abc');
|
|
39
39
|
}));
|
|
40
40
|
it('POST /profile/access-key/rotate returns a new accessKey', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
41
|
sinon_1.default.stub(typedi_1.Container.get(UserService_1.UserService), 'rotateAccessKey').resolves({ id: 'u1', accessKey: 'xen_NEW' });
|
|
42
42
|
const app = appWithAuth({ userId: 'u1', role: 'ADMIN', scopes: 'devices,sessions,read' });
|
|
43
|
-
const r = yield (0,
|
|
43
|
+
const r = yield (0, loopbackRequest_1.default)(app).post('/profile/access-key/rotate');
|
|
44
44
|
(0, chai_1.expect)(r.body.accessKey).to.equal('xen_NEW');
|
|
45
45
|
}));
|
|
46
46
|
it('GET /profile/tokens lists caller tokens without secrets', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -48,7 +48,7 @@ describe('profile router', () => {
|
|
|
48
48
|
{ id: 'k1', name: 'CI', scopes: 'sessions,read', expiresAt: null, createdAt: new Date(), lastUsedAt: null },
|
|
49
49
|
]);
|
|
50
50
|
const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
|
|
51
|
-
const r = yield (0,
|
|
51
|
+
const r = yield (0, loopbackRequest_1.default)(app).get('/profile/tokens');
|
|
52
52
|
(0, chai_1.expect)(r.body[0].name).to.equal('CI');
|
|
53
53
|
(0, chai_1.expect)(r.body[0].keyHash).to.be.undefined;
|
|
54
54
|
}));
|
|
@@ -56,14 +56,14 @@ describe('profile router', () => {
|
|
|
56
56
|
const create = sinon_1.default.stub(typedi_1.Container.get(ApiKeyService_1.ApiKeyService), 'create')
|
|
57
57
|
.resolves({ id: 'k2', raw: 'rawsecret' });
|
|
58
58
|
const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
|
|
59
|
-
const r = yield (0,
|
|
59
|
+
const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI' });
|
|
60
60
|
(0, chai_1.expect)(r.status).to.equal(201);
|
|
61
61
|
(0, chai_1.expect)(r.body.token).to.equal('rawsecret');
|
|
62
62
|
(0, chai_1.expect)(create.firstCall.args[0].scopes).to.deep.equal(['sessions', 'read']);
|
|
63
63
|
}));
|
|
64
64
|
it('POST /profile/tokens rejects an attempt to widen scopes', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
65
65
|
const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
|
|
66
|
-
const r = yield (0,
|
|
66
|
+
const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI', scopes: ['admin'] });
|
|
67
67
|
(0, chai_1.expect)(r.status).to.equal(400);
|
|
68
68
|
(0, chai_1.expect)(r.body.error).to.match(/cannot widen/);
|
|
69
69
|
}));
|
|
@@ -73,7 +73,7 @@ describe('profile router', () => {
|
|
|
73
73
|
.resolves({ id: 'k3', raw: 'rawsecret' });
|
|
74
74
|
const future = new Date(Date.now() + 30 * 24 * 60 * 60000).toISOString();
|
|
75
75
|
const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
|
|
76
|
-
const r = yield (0,
|
|
76
|
+
const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI', expiresAt: future });
|
|
77
77
|
(0, chai_1.expect)(r.status).to.equal(201);
|
|
78
78
|
(0, chai_1.expect)(r.body.expiresAt).to.equal(future);
|
|
79
79
|
(0, chai_1.expect)((_a = create.firstCall.args[0].expiresAt) === null || _a === void 0 ? void 0 : _a.toISOString()).to.equal(future);
|
|
@@ -81,13 +81,13 @@ describe('profile router', () => {
|
|
|
81
81
|
it('POST /profile/tokens rejects a past expiresAt with 400', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
82
|
const past = new Date(Date.now() - 60000).toISOString();
|
|
83
83
|
const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
|
|
84
|
-
const r = yield (0,
|
|
84
|
+
const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI', expiresAt: past });
|
|
85
85
|
(0, chai_1.expect)(r.status).to.equal(400);
|
|
86
86
|
(0, chai_1.expect)(r.body.error).to.match(/future/);
|
|
87
87
|
}));
|
|
88
88
|
it('POST /profile/tokens rejects an unparseable expiresAt with 400', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
89
89
|
const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
|
|
90
|
-
const r = yield (0,
|
|
90
|
+
const r = yield (0, loopbackRequest_1.default)(app).post('/profile/tokens').send({ name: 'CI', expiresAt: 'not-a-date' });
|
|
91
91
|
(0, chai_1.expect)(r.status).to.equal(400);
|
|
92
92
|
(0, chai_1.expect)(r.body.error).to.match(/ISO-8601/);
|
|
93
93
|
}));
|
|
@@ -95,14 +95,14 @@ describe('profile router', () => {
|
|
|
95
95
|
sinon_1.default.stub(prisma_1.prisma.apiKey, 'findFirst').resolves({ id: 'k1', userId: 'u1' });
|
|
96
96
|
const revoke = sinon_1.default.stub(typedi_1.Container.get(ApiKeyService_1.ApiKeyService), 'revoke').resolves(undefined);
|
|
97
97
|
const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
|
|
98
|
-
const r = yield (0,
|
|
98
|
+
const r = yield (0, loopbackRequest_1.default)(app).delete('/profile/tokens/k1');
|
|
99
99
|
(0, chai_1.expect)(r.status).to.equal(204);
|
|
100
100
|
(0, chai_1.expect)(revoke.calledOnceWithExactly('k1')).to.be.true;
|
|
101
101
|
}));
|
|
102
102
|
it('DELETE /profile/tokens/:id 404s when the token belongs to someone else', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
103
|
sinon_1.default.stub(prisma_1.prisma.apiKey, 'findFirst').resolves(null);
|
|
104
104
|
const app = appWithAuth({ userId: 'u1', role: 'MEMBER', scopes: 'sessions,read' });
|
|
105
|
-
const r = yield (0,
|
|
105
|
+
const r = yield (0, loopbackRequest_1.default)(app).delete('/profile/tokens/k1');
|
|
106
106
|
(0, chai_1.expect)(r.status).to.equal(404);
|
|
107
107
|
}));
|
|
108
108
|
});
|
|
@@ -61,6 +61,7 @@ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
|
61
61
|
const config_1 = require("../../src/config");
|
|
62
62
|
const prisma_1 = require("../../src/prisma");
|
|
63
63
|
const container_registration_1 = require("../helpers/container-registration");
|
|
64
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
64
65
|
/**
|
|
65
66
|
* createSession writes who asked onto the pending-session row, so the queue
|
|
66
67
|
* can show a member her own and her team's requests. It goes on that row only:
|
|
@@ -79,6 +80,10 @@ const caps = (extra = {}) => ({
|
|
|
79
80
|
});
|
|
80
81
|
const withKey = (accessKey, extra = {}) => caps(Object.assign({ 'xe:options': { accessKey, token: 'tk' } }, extra));
|
|
81
82
|
describe('createSession records who asked on the pending-session row', () => {
|
|
83
|
+
// The in-memory stores, also when this file runs on its own: without
|
|
84
|
+
// NODE_ENV=test the factory handed out the Prisma stores, which are the
|
|
85
|
+
// developer's ~/.cache/xenon/xenon.db.
|
|
86
|
+
(0, loki_stores_1.useLokiStores)();
|
|
82
87
|
let svc;
|
|
83
88
|
let device;
|
|
84
89
|
let context;
|
|
@@ -15,7 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
require("reflect-metadata");
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const express_1 = __importDefault(require("express"));
|
|
18
|
-
const
|
|
18
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
19
19
|
const sinon_1 = __importDefault(require("sinon"));
|
|
20
20
|
const grid_1 = __importDefault(require("../../src/app/routers/grid"));
|
|
21
21
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
@@ -120,24 +120,24 @@ describe('the session queue is team-scoped: own teams + a count', () => {
|
|
|
120
120
|
afterEach(() => sinon_1.default.restore());
|
|
121
121
|
describe('a member in team A', () => {
|
|
122
122
|
it("GET /queue shows the request for her team's phone and her teammate's, nothing else", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
|
-
const r = yield (0,
|
|
123
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue');
|
|
124
124
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
125
125
|
(0, chai_1.expect)(ids(r.body)).to.deep.equal(['targets-a', 'untargeted-a']);
|
|
126
126
|
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('bob-secret-release');
|
|
127
127
|
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('bob-targeted');
|
|
128
128
|
}));
|
|
129
129
|
it('GET /queue never returns who asked', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
130
|
-
const r = yield (0,
|
|
130
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue');
|
|
131
131
|
for (const shown of r.body)
|
|
132
132
|
(0, chai_1.expect)(shown).to.not.have.property(REQUESTER);
|
|
133
133
|
}));
|
|
134
134
|
it('GET /queue/length is the whole queue, the number every caller gets', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
-
const r = yield (0,
|
|
135
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue/length');
|
|
136
136
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
137
137
|
(0, chai_1.expect)(r.body).to.equal(4);
|
|
138
138
|
}));
|
|
139
139
|
it('GET /queue/summary agrees with /queue/length, and counts what she cannot see', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
140
|
-
const r = yield (0,
|
|
140
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue/summary');
|
|
141
141
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
142
142
|
(0, chai_1.expect)(r.body.total).to.equal(4);
|
|
143
143
|
(0, chai_1.expect)(r.body.otherCount).to.equal(2);
|
|
@@ -145,17 +145,17 @@ describe('the session queue is team-scoped: own teams + a count', () => {
|
|
|
145
145
|
(0, chai_1.expect)(byPlatform.reduce((n, p) => n + p.count, 0)).to.equal(4);
|
|
146
146
|
}));
|
|
147
147
|
it("GET /queue/status answers for another team's request exactly as for an unknown id", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
148
|
-
const unknown = yield (0,
|
|
148
|
+
const unknown = yield (0, loopbackRequest_1.default)(alice()).get('/queue/status/no-such-request');
|
|
149
149
|
(0, chai_1.expect)(unknown.status).to.equal(404);
|
|
150
150
|
for (const hidden of ['targets-b', 'untargeted-b']) {
|
|
151
|
-
const r = yield (0,
|
|
151
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get(`/queue/status/${hidden}`);
|
|
152
152
|
(0, chai_1.expect)(r.status, hidden).to.equal(404);
|
|
153
153
|
(0, chai_1.expect)(r.body, hidden).to.deep.equal(unknown.body);
|
|
154
154
|
}
|
|
155
155
|
}));
|
|
156
156
|
it('GET /queue/status still answers for the requests she can see', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
157
|
for (const shown of ['targets-a', 'untargeted-a']) {
|
|
158
|
-
const r = yield (0,
|
|
158
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get(`/queue/status/${shown}`);
|
|
159
159
|
(0, chai_1.expect)(r.status, shown).to.equal(200);
|
|
160
160
|
(0, chai_1.expect)(r.body.platform, shown).to.equal('android');
|
|
161
161
|
}
|
|
@@ -163,25 +163,25 @@ describe('the session queue is team-scoped: own teams + a count', () => {
|
|
|
163
163
|
});
|
|
164
164
|
describe('an admin', () => {
|
|
165
165
|
it('GET /queue shows all four', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
166
|
-
const r = yield (0,
|
|
166
|
+
const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue');
|
|
167
167
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
168
168
|
(0, chai_1.expect)(ids(r.body)).to.deep.equal(['targets-a', 'targets-b', 'untargeted-a', 'untargeted-b']);
|
|
169
169
|
for (const shown of r.body)
|
|
170
170
|
(0, chai_1.expect)(shown).to.not.have.property(REQUESTER);
|
|
171
171
|
}));
|
|
172
172
|
it('with no lookup of phones or teams', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
173
|
-
yield (0,
|
|
173
|
+
yield (0, loopbackRequest_1.default)(admin()).get('/queue');
|
|
174
174
|
(0, chai_1.expect)(prisma_1.prisma.device.findMany.called).to.equal(false);
|
|
175
175
|
(0, chai_1.expect)(prisma_1.prisma.teamMember.findMany.called).to.equal(false);
|
|
176
176
|
}));
|
|
177
177
|
it('GET /queue/summary counts nothing as hidden', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
178
|
-
const r = yield (0,
|
|
178
|
+
const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue/summary');
|
|
179
179
|
(0, chai_1.expect)(r.body.total).to.equal(4);
|
|
180
180
|
(0, chai_1.expect)(r.body.otherCount).to.equal(0);
|
|
181
181
|
}));
|
|
182
182
|
it('GET /queue/length and /queue/status answer as before', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
183
|
-
(0, chai_1.expect)((yield (0,
|
|
184
|
-
const r = yield (0,
|
|
183
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(admin()).get('/queue/length')).body).to.equal(4);
|
|
184
|
+
const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue/status/untargeted-b');
|
|
185
185
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
186
186
|
}));
|
|
187
187
|
});
|
|
@@ -195,13 +195,14 @@ describe('the session queue is team-scoped: own teams + a count', () => {
|
|
|
195
195
|
['an admin', admin],
|
|
196
196
|
]) {
|
|
197
197
|
it(`are never returned to ${who}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
198
|
-
const r = yield (0,
|
|
198
|
+
const r = yield (0, loopbackRequest_1.default)(caller()).get('/queue');
|
|
199
199
|
const shown = r.body.find((x) => x.capability_id === 'untargeted-a');
|
|
200
200
|
(0, chai_1.expect)(shown, 'the request itself is shown').to.not.equal(undefined);
|
|
201
201
|
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('amy-api-token');
|
|
202
202
|
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('amy.session.jwt');
|
|
203
|
-
//
|
|
204
|
-
(0, chai_1.expect)(
|
|
203
|
+
// Nor the access key: half of the pair, and it names Amy's key.
|
|
204
|
+
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('ak_amy');
|
|
205
|
+
// What isn't a credential still reads as before.
|
|
205
206
|
(0, chai_1.expect)(shown['xenon:options'].sessionName).to.equal('amy-login');
|
|
206
207
|
}));
|
|
207
208
|
}
|
|
@@ -102,7 +102,7 @@ describe('queue visibility', () => {
|
|
|
102
102
|
(0, chai_1.expect)(yield seen([untargeted, targeted], alice)).to.deep.equal([false, false]);
|
|
103
103
|
}));
|
|
104
104
|
it('a credential narrowed to her team, whatever teams its user is in', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
105
|
-
// A team-bound key, a session token's teamId claim, or
|
|
105
|
+
// A team-bound key, a session token's teamId claim, or xe:options.team.
|
|
106
106
|
(0, chai_1.expect)(yield seen([req({ userId: 'bob', teamId: 'team-a' })], alice)).to.deep.equal([true]);
|
|
107
107
|
}));
|
|
108
108
|
it("not a teammate's request narrowed to another team", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -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 fs = __importStar(require("fs"));
|
|
54
54
|
const os = __importStar(require("os"));
|
|
55
55
|
const path = __importStar(require("path"));
|
|
@@ -104,7 +104,7 @@ function binary(res, cb) {
|
|
|
104
104
|
}
|
|
105
105
|
function get(caller_1, url_1) {
|
|
106
106
|
return __awaiter(this, arguments, void 0, function* (caller, url, headers = {}) {
|
|
107
|
-
let req = (0,
|
|
107
|
+
let req = (0, loopbackRequest_1.default)(buildApp(caller)).get(`/xenon/api/recordings/${GROUP}/${url}`);
|
|
108
108
|
for (const [k, v] of Object.entries(headers))
|
|
109
109
|
req = req.set(k, v);
|
|
110
110
|
const res = yield req.buffer(true).parse(binary);
|
|
@@ -21,6 +21,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
21
21
|
const api_1 = require("@opentelemetry/api");
|
|
22
22
|
const attributes_1 = require("../../src/services/telemetry/attributes");
|
|
23
23
|
const artifact_store_1 = require("../helpers/artifact-store");
|
|
24
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
24
25
|
// Stub metrics + trace BEFORE the module loads so the module-level
|
|
25
26
|
// ensureOtelInstruments() picks up our mocks. Inline import after stubbing.
|
|
26
27
|
function makeMockMeter() {
|
|
@@ -56,6 +57,10 @@ function makeMockSpan() {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
describe('RecordingOrchestrator instrumentation', () => {
|
|
60
|
+
// The in-memory stores, also when this file runs on its own: without
|
|
61
|
+
// NODE_ENV=test the factory handed out the Prisma stores, which are the
|
|
62
|
+
// developer's ~/.cache/xenon/xenon.db.
|
|
63
|
+
(0, loki_stores_1.useLokiStores)();
|
|
59
64
|
// RecordingOrchestrator resolves ARTIFACT_STORE from the container at runtime.
|
|
60
65
|
(0, artifact_store_1.useArtifactStore)();
|
|
61
66
|
let getMeterStub;
|
|
@@ -21,6 +21,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
21
21
|
const RecordingOrchestrator_1 = require("../../src/services/recording/RecordingOrchestrator");
|
|
22
22
|
const concurrency_gate_1 = require("../../src/services/recording/concurrency-gate");
|
|
23
23
|
const artifact_store_1 = require("../helpers/artifact-store");
|
|
24
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
24
25
|
// Issue #203. When the ffmpeg process for a recording exits on its own nothing
|
|
25
26
|
// told the orchestrator, so the row sat at RECORDING — no ended_at, no duration,
|
|
26
27
|
// no size — until a human clicked Stop. Observed live during the #200 cable-pull
|
|
@@ -69,6 +70,10 @@ function makeOrch(row, overrides = {}) {
|
|
|
69
70
|
return { orch, store, gate, videoPipeline, eventMgr, unblockDeviceFn, probeDurationMsFn };
|
|
70
71
|
}
|
|
71
72
|
describe('RecordingOrchestrator: ffmpeg exiting on its own', () => {
|
|
73
|
+
// The in-memory stores, also when this file runs on its own: without
|
|
74
|
+
// NODE_ENV=test the factory handed out the Prisma stores, which are the
|
|
75
|
+
// developer's ~/.cache/xenon/xenon.db.
|
|
76
|
+
(0, loki_stores_1.useLokiStores)();
|
|
72
77
|
// compositeOutputPath and per-device paths resolve through ArtifactStore.
|
|
73
78
|
(0, artifact_store_1.useArtifactStore)();
|
|
74
79
|
afterEach(() => sinon_1.default.restore());
|
|
@@ -13,12 +13,18 @@ require("reflect-metadata");
|
|
|
13
13
|
const chai_1 = require("chai");
|
|
14
14
|
const recording_store_1 = require("../../src/services/recording/recording-store");
|
|
15
15
|
const prisma_1 = require("../../src/prisma");
|
|
16
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
16
17
|
describe('RecordingStore (Prisma round-trip)', () => {
|
|
18
|
+
// Its own migrated database: the round trip wrote these rows to the
|
|
19
|
+
// developer's ~/.cache/xenon/xenon.db, and failed where it lacked the tables.
|
|
20
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
17
21
|
const store = new recording_store_1.RecordingStore();
|
|
22
|
+
// Through the scratch client itself: the helper's own afterEach, which
|
|
23
|
+
// runs first, has already put `prisma` back on the server's database.
|
|
18
24
|
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
-
yield
|
|
20
|
-
yield
|
|
21
|
-
yield
|
|
25
|
+
yield scratch.db.annotation.deleteMany({ where: { recording_id: { contains: 'test-' } } });
|
|
26
|
+
yield scratch.db.bookmark.deleteMany({ where: { recording_id: { contains: 'test-' } } });
|
|
27
|
+
yield scratch.db.recording.deleteMany({ where: { group_id: { startsWith: 'test-' } } });
|
|
22
28
|
}));
|
|
23
29
|
it('creates a recording row with status=RECORDING', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
30
|
const rec = yield store.create({
|