@xenon-device-management/xenon 2.14.0 → 2.16.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 +44 -29
- package/lib/package.json +6 -5
- package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-GmckX_7r.js} +1 -1
- package/lib/public/assets/DeviceMosaicView-DqODzkyo.js +2 -0
- package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-7hETKEWb.js} +1 -1
- package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-GOgssFEP.js} +1 -1
- package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-Yvf6RGMd.js} +1 -1
- package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-KrPnfdsf.js} +1 -1
- package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-C6WjZFeI.js} +1 -1
- package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CSRIhZEh.js} +1 -1
- package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-CB8ST3nE.js} +1 -1
- package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C9o90KId.js} +1 -1
- package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-CPm3w9l2.js} +1 -1
- package/lib/public/assets/{Table-BcTm5xV2.js → Table-DwIgERRt.js} +1 -1
- package/lib/public/assets/{activity-DOVdsfMQ.js → activity-DrLbHifn.js} +1 -1
- package/lib/public/assets/ai-settings-DU_Ls06P.js +16 -0
- package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-CWYdEzJ_.js} +1 -1
- package/lib/public/assets/{apps-D0SC6uw6.js → apps-BqNVdln7.js} +1 -1
- package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BtB0_Jg_.js} +1 -1
- package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-C8NhdtyN.js} +1 -1
- package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-gMg0ffvv.js} +1 -1
- package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CiYZ3N4M.js} +2 -2
- package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-EaaqDmLN.js} +1 -1
- package/lib/public/assets/{button-Dda4irn_.js → button-DDau4Axv.js} +1 -1
- package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-DQ83B1ed.js} +1 -1
- package/lib/public/assets/{check-CZ9iw9-e.js → check-K1ovTQam.js} +1 -1
- package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-Biq00pMt.js} +1 -1
- package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CBv88cH7.js} +1 -1
- package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-Bmltailn.js} +1 -1
- package/lib/public/assets/{clock-CpuDKysU.js → clock-BPrld-3h.js} +1 -1
- package/lib/public/assets/{copy-CAQydNFe.js → copy-t0QB14JI.js} +1 -1
- package/lib/public/assets/derive-UnLRp4r3.js +1 -0
- package/lib/public/assets/device-explorer-BgGZ8AvV.js +282 -0
- package/lib/public/assets/{download-C9XG6lLv.js → download-BroHxNOh.js} +1 -1
- package/lib/public/assets/forgot-password-Bjmf49i6.js +1 -0
- package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-DeD__6wa.js} +6 -6
- package/lib/public/assets/{index-DJGc1M2f.js → index-72oF89LJ.js} +1 -1
- package/lib/public/assets/index-B0JeuZbg.js +15 -0
- package/lib/public/assets/index-B0gQkEg0.js +219 -0
- package/lib/public/assets/{input-Cm0tmSoc.js → input-DIc4MSC7.js} +1 -1
- package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-BcWm2vW8.js} +1 -1
- package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-B06IJJad.js} +1 -1
- package/lib/public/assets/{lock-CCJv9m_I.js → lock-pVLvnN30.js} +1 -1
- package/lib/public/assets/{login-f8vOVW-U.js → login-CN77vy--.js} +1 -1
- package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-CSdzQSkY.js} +1 -1
- package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-BtrN5IkI.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
- package/lib/public/assets/{network-ebT86qoi.js → network-BSfA8ZxG.js} +1 -1
- package/lib/public/assets/{overview-C0aykVrd.js → overview-BfTebCK5.js} +1 -1
- package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-D8KUX6Qg.js} +1 -1
- package/lib/public/assets/{play-DLG9t8FU.js → play-Cvc8xzic.js} +1 -1
- package/lib/public/assets/{plus-nBFSMu_X.js → plus-C9Zj84UF.js} +1 -1
- package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-Bi27STgs.js} +1 -1
- package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-CPsOuwSu.js} +1 -1
- package/lib/public/assets/runbook-page-DEQ-DBOX.js +304 -0
- package/lib/public/assets/{select-88jHcl-Z.js → select-BWpnhWXj.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BefSCSpe.js} +1 -1
- package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-MSk96Zz7.js} +1 -1
- package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
- package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
- package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-V6x-cXwy.js} +1 -1
- package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-Duex-GZD.js} +1 -1
- package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BBcoqBmL.js} +1 -1
- package/lib/public/assets/{teams-B4-jrr-1.js → teams-ts8p5rEh.js} +1 -1
- package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-CZ2ew6AZ.js} +1 -1
- package/lib/public/assets/{upload-BG7_rMXK.js → upload-Dv99y_le.js} +1 -1
- package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-DrYC8JCF.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
- package/lib/public/assets/{users-CFba5TSy.js → users-D7tKMESH.js} +1 -1
- package/lib/public/assets/{users-Cp0beJm3.js → users-gJ5Y01yo.js} +1 -1
- package/lib/public/assets/{video-off-DDsEISjV.js → video-off-CqZVuZGs.js} +1 -1
- package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-DI5DSW99.js} +1 -1
- package/lib/public/assets/{zap-l4VaXzq8.js → zap-ClcHFxfo.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +16 -12
- package/lib/src/InternalHttpClient.js +27 -5
- package/lib/src/XenonCapabilityManager.js +10 -3
- package/lib/src/app/dashboardPluginLink.js +117 -0
- package/lib/src/app/index.js +13 -34
- package/lib/src/app/openapi/control.yaml +6 -4
- package/lib/src/app/openapi/grid.yaml +8 -2
- package/lib/src/app/openapi/identity.yaml +103 -31
- package/lib/src/app/openapi/platform.yaml +33 -15
- package/lib/src/app/openapi/sessions.yaml +12 -3
- package/lib/src/app/routers/apikeys.js +14 -2
- package/lib/src/app/routers/auth.js +31 -6
- package/lib/src/app/routers/control.js +13 -5
- package/lib/src/app/routers/dashboard.js +42 -27
- package/lib/src/app/routers/grid.js +5 -3
- package/lib/src/app/routers/profile.js +21 -0
- package/lib/src/app/routers/users.js +12 -6
- package/lib/src/app/swagger.js +2 -0
- package/lib/src/app/ws/h264StreamWs.js +10 -1
- package/lib/src/app/ws/nodeSocketRelay.js +77 -11
- package/lib/src/config.js +1 -0
- package/lib/src/dashboard/commandLogFields.js +42 -0
- package/lib/src/dashboard/commands.js +5 -5
- package/lib/src/dashboard/event-manager.js +64 -204
- package/lib/src/dashboard/services/failure-analysis-service.js +118 -105
- package/lib/src/dashboard/services/failureCategories.js +264 -0
- package/lib/src/dashboard/sessionCommandSummary.js +39 -0
- package/lib/src/data-service/device-store.js +14 -1
- package/lib/src/data-service/prisma-store.js +19 -7
- package/lib/src/data-service/web-config-service.js +7 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
- package/lib/src/device-managers/IOSDeviceManager.js +10 -0
- package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
- package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
- package/lib/src/device-utils.js +29 -3
- package/lib/src/gateway/defaultGateway.js +1 -1
- package/lib/src/gateway/forwardToNode.js +6 -11
- package/lib/src/gateway/healReport.js +87 -0
- package/lib/src/gateway/hubSessionToken.js +27 -4
- package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
- package/lib/src/gateway/sessionGateway.js +12 -2
- package/lib/src/generated/client/edge.js +4 -3
- package/lib/src/generated/client/index-browser.js +1 -0
- package/lib/src/generated/client/index.d.ts +32 -0
- package/lib/src/generated/client/index.js +4 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +3 -0
- package/lib/src/generated/client/wasm.js +1 -0
- package/lib/src/helpers/outboundProxy.js +219 -0
- package/lib/src/index.js +21 -0
- package/lib/src/interceptors/CommandInterceptor.js +256 -196
- package/lib/src/interceptors/xenonScripts.js +28 -7
- package/lib/src/logger.js +3 -1
- package/lib/src/middleware/authMiddleware.js +15 -3
- package/lib/src/middleware/commandCaller.js +3 -3
- package/lib/src/middleware/verifyCredential.js +58 -1
- package/lib/src/plugin.js +16 -6
- package/lib/src/scripts/database-check.js +3 -3
- package/lib/src/scripts/initialize-database.js +35 -33
- package/lib/src/scripts/run-migrations.js +1079 -27
- package/lib/src/services/AICommandService.js +9 -2
- package/lib/src/services/AIService.js +192 -42
- package/lib/src/services/ApiKeyService.js +16 -5
- package/lib/src/services/EmailService.js +29 -0
- package/lib/src/services/InterceptorService.js +6 -2
- package/lib/src/services/OrphanSweeper.js +8 -0
- package/lib/src/services/SelectorStateService.js +11 -7
- package/lib/src/services/SelectorVerificationJob.js +21 -6
- package/lib/src/services/ServerManager.js +11 -1
- package/lib/src/services/SessionLifecycleService.js +118 -44
- package/lib/src/services/SocketClient.js +11 -0
- package/lib/src/services/SocketServer.js +380 -45
- package/lib/src/services/TeamService.js +5 -1
- package/lib/src/services/TracingService.js +171 -60
- package/lib/src/services/UserService.js +14 -3
- package/lib/src/services/UserSessionService.js +16 -4
- package/lib/src/services/bug-report/video-slice.js +4 -1
- package/lib/src/services/device-access/leaseOverride.js +7 -5
- package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
- package/lib/src/services/healing/HealEtalonService.js +3 -2
- package/lib/src/services/healing/HealingOrchestrator.js +57 -72
- package/lib/src/services/healing/OcrHealingProvider.js +41 -64
- package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
- package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
- package/lib/src/services/healing/fingerprintIdentity.js +126 -0
- package/lib/src/services/healing/resilioPath.js +274 -0
- package/lib/src/services/identity/identityChanges.js +41 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +358 -0
- package/lib/src/services/logcat/deviceLogBook.js +164 -0
- package/lib/src/services/logcat/iosDriverLog.js +178 -0
- package/lib/src/services/ocr/ocrData.js +118 -0
- package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
- package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
- package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
- package/lib/src/services/passwordResetLink.js +50 -9
- package/lib/src/services/passwordResetMode.js +3 -1
- package/lib/src/services/publicUrl.js +45 -0
- package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
- package/lib/src/services/session/sessionIdentity.js +5 -3
- package/lib/src/services/session/shutdownReason.js +26 -0
- package/lib/src/services/settings/aiEngineSettings.js +219 -0
- package/lib/src/services/token/JwtKeyService.js +8 -2
- package/lib/src/services/token/mcpTokenTtl.js +27 -0
- package/lib/src/services/token/mintedLifetime.js +51 -0
- package/lib/src/sessions/LocalSession.js +29 -1
- package/lib/src/sessions/RemoteSession.js +12 -2
- package/lib/src/sessions/SessionManager.js +4 -1
- package/lib/src/sessions/sessionMemory.js +25 -0
- package/lib/test/helpers/driver-log.js +38 -0
- package/lib/test/helpers/fake-ai-provider.js +188 -0
- package/lib/test/helpers/fake-http-proxy.js +106 -0
- package/lib/test/helpers/loki-stores.js +33 -0
- package/lib/test/helpers/otlp-probe.js +107 -0
- package/lib/test/helpers/page-sources.js +164 -0
- package/lib/test/helpers/scratch-database.js +57 -13
- package/lib/test/integration/androidDevices.spec.js +1 -1
- package/lib/test/integration/audit-ingest.spec.js +10 -10
- package/lib/test/integration/auth-flow.spec.js +9 -14
- package/lib/test/integration/auth-rate-limit.spec.js +6 -3
- package/lib/test/integration/bug-report-route.spec.js +22 -2
- package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
- package/lib/test/integration/forgot-password.spec.js +17 -18
- package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
- package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
- package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
- package/lib/test/integration/migration.spec.js +6 -1
- package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
- package/lib/test/integration/ports-router.spec.js +4 -4
- package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
- package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
- package/lib/test/integration/profile-tokens.spec.js +10 -16
- package/lib/test/integration/recordings-router.spec.js +16 -16
- package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
- package/lib/test/integration/role-matrix.spec.js +4 -9
- package/lib/test/integration/sdk-leases-router.spec.js +9 -9
- package/lib/test/integration/sdk-version.spec.js +2 -2
- package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
- package/lib/test/integration/selector-state-flow.spec.js +5 -5
- package/lib/test/integration/team-membership-migration.spec.js +73 -12
- package/lib/test/integration/team-visibility-apps.spec.js +15 -22
- package/lib/test/integration/team-visibility-control.spec.js +96 -69
- package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
- package/lib/test/integration/team-visibility-grid.spec.js +16 -15
- package/lib/test/integration/team-visibility-leases.spec.js +8 -11
- package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
- package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
- package/lib/test/integration/users-flow.spec.js +7 -12
- package/lib/test/integration/users-lockout.spec.js +5 -3
- package/lib/test/unit/EmailService.test.js +66 -0
- package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
- package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
- package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
- package/lib/test/unit/ai-engine-settings.spec.js +338 -0
- package/lib/test/unit/ai-provider-switch.spec.js +130 -0
- package/lib/test/unit/ai-rate-limit.spec.js +126 -0
- package/lib/test/unit/android-adb-retry.spec.js +112 -0
- package/lib/test/unit/android-h264-viewers.spec.js +272 -0
- package/lib/test/unit/android-preview-hold.spec.js +3 -2
- package/lib/test/unit/apikeys-router.spec.js +84 -0
- package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
- package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
- package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
- package/lib/test/unit/command-log-network-capture.spec.js +191 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +255 -0
- package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
- package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
- package/lib/test/unit/database-check.spec.js +1 -0
- package/lib/test/unit/device-log-book.spec.js +180 -0
- package/lib/test/unit/device-utils.spec.js +13 -4
- package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
- package/lib/test/unit/event-log-session-data.spec.js +251 -0
- package/lib/test/unit/failure-analysis.spec.js +196 -0
- package/lib/test/unit/failure-categories.spec.js +130 -0
- package/lib/test/unit/failure-categorization.spec.js +393 -0
- package/lib/test/unit/fingerprint-identity.spec.js +433 -0
- package/lib/test/unit/h264-multiplexer.spec.js +46 -0
- package/lib/test/unit/heal-report.spec.js +71 -0
- package/lib/test/unit/healing-instrumentation.spec.js +6 -1
- package/lib/test/unit/healing-tier-gate.spec.js +74 -29
- package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +243 -1
- package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
- package/lib/test/unit/ios-driver-log.spec.js +168 -0
- package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
- package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
- package/lib/test/unit/lease/lease-override.spec.js +23 -6
- package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
- package/lib/test/unit/logcat-ws.spec.js +78 -37
- package/lib/test/unit/logger-json-levels.spec.js +44 -0
- package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
- package/lib/test/unit/observability-example-loki.spec.js +25 -0
- package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
- package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
- package/lib/test/unit/ocr-offline.spec.js +213 -0
- package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
- package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
- package/lib/test/unit/omni-vision.spec.js +5 -7
- package/lib/test/unit/openapi-for-site.spec.js +80 -0
- package/lib/test/unit/outbound-proxy.spec.js +320 -0
- package/lib/test/unit/package-engines.spec.js +27 -0
- package/lib/test/unit/passwordResetMode.test.js +10 -2
- package/lib/test/unit/port-exhaustion.spec.js +2 -1
- package/lib/test/unit/profile-router.test.js +122 -1
- package/lib/test/unit/proxy-option.spec.js +358 -0
- package/lib/test/unit/queue-requester.spec.js +1 -1
- package/lib/test/unit/reset-link-address.spec.js +266 -0
- package/lib/test/unit/reset-link.test.js +10 -4
- package/lib/test/unit/resilio-path-healing.spec.js +270 -0
- package/lib/test/unit/run-migrations-database.spec.js +807 -0
- package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
- package/lib/test/unit/run-migrations.test.js +864 -0
- package/lib/test/unit/selector-health-actions.spec.js +3 -1
- package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
- package/lib/test/unit/selector-state-service.spec.js +72 -23
- package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
- package/lib/test/unit/selector-verification-job.spec.js +28 -19
- package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
- package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
- package/lib/test/unit/self-healing-boot.spec.js +10 -5
- package/lib/test/unit/self-healing-setting.spec.js +7 -6
- package/lib/test/unit/session-app-resolution.spec.js +5 -1
- package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
- package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
- package/lib/test/unit/session-credential-strip.spec.js +1 -1
- package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
- package/lib/test/unit/session-device-logs.spec.js +356 -0
- package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
- package/lib/test/unit/session-log-indexes.spec.js +64 -3
- package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
- package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
- package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
- package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
- package/lib/test/unit/token-mint-limits.spec.js +165 -0
- package/lib/test/unit/tracing-export-routing.spec.js +137 -0
- package/lib/test/unit/tracing-session-spans.spec.js +337 -0
- package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
- package/lib/test/unit/verify-credential.spec.js +33 -0
- package/lib/test/unit/vision-assertion.spec.js +3 -2
- package/lib/test/unit/web-config-service.spec.js +19 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
- package/prisma/schema.prisma +3 -0
- package/schema.json +16 -12
- package/scripts/dev/readme-screenshots.js +276 -23
- package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
- package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
- package/scripts/dev/screenshot-fixtures/session.js +242 -0
- package/scripts/export-openapi.js +32 -0
- package/scripts/lib/openapi-for-site.js +79 -0
- package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
- package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
- package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
- package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
- package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
- package/lib/public/assets/index-BbKEXUFH.js +0 -219
- package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
- package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
- package/lib/src/scripts/prepare-prisma.js +0 -89
- /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
|
@@ -32,10 +32,17 @@ const selectorKeys_1 = require("./selector-health/selectorKeys");
|
|
|
32
32
|
const log = logger_1.default.scope('SelectorVerification');
|
|
33
33
|
/**
|
|
34
34
|
* Threshold of distinct clean build_ids required to promote a Pending
|
|
35
|
-
* SelectorState row to Resolved. "Clean" means
|
|
36
|
-
*
|
|
35
|
+
* SelectorState row to Resolved. "Clean" means the selector was found without
|
|
36
|
+
* healing in the build: a findElement of the (strategy, selector) tuple that
|
|
37
|
+
* answered an element, or a findElements that answered at least one, and no
|
|
38
|
+
* heal of it. Through 2.14 a find that failed outright counted too, so a
|
|
39
|
+
* selector that was never found again was verified as fixed.
|
|
37
40
|
*/
|
|
38
41
|
const CLEAN_BUILDS_TO_RESOLVE = 3;
|
|
42
|
+
/** The selector a row is about, as the socket server's scoped emit names it. */
|
|
43
|
+
function keyOfRow(row) {
|
|
44
|
+
return { strategy: row.original_strategy, selector: row.original_selector };
|
|
45
|
+
}
|
|
39
46
|
/**
|
|
40
47
|
* Periodically scans Pending SelectorState rows and promotes them to Resolved
|
|
41
48
|
* once enough clean CI builds have been observed (no heals on the selector
|
|
@@ -127,12 +134,20 @@ let SelectorVerificationJob = class SelectorVerificationJob {
|
|
|
127
134
|
// that ran under a CI build (build_id IS NOT NULL); ad-hoc local runs
|
|
128
135
|
// intentionally don't move verification forward.
|
|
129
136
|
//
|
|
137
|
+
// A find that found the selector is one that didn't fail (`is_error`,
|
|
138
|
+
// the dashboard's record of an error answer) and didn't answer an empty
|
|
139
|
+
// list (`{"value":[]`, findElements finding nothing). The command log
|
|
140
|
+
// keeps the answer as JSON.stringify wrote it, or as the node sent it.
|
|
141
|
+
//
|
|
130
142
|
// Identifier names (PascalCase tables, camelCase createdAt) match the
|
|
131
143
|
// actual SQLite schema generated by Prisma — see
|
|
132
144
|
// `sqlite3 prisma/dev.db ".schema SessionLog"`.
|
|
133
145
|
const heals = yield this.prisma.$queryRaw `
|
|
134
146
|
SELECT s."build_id" AS build_id,
|
|
135
|
-
MAX(CASE WHEN sl."is_healed" THEN 1 ELSE 0 END) AS healed
|
|
147
|
+
MAX(CASE WHEN sl."is_healed" THEN 1 ELSE 0 END) AS healed,
|
|
148
|
+
MAX(CASE WHEN NOT sl."is_healed" AND NOT sl."is_error"
|
|
149
|
+
AND sl."response" NOT LIKE '{"value":[]%'
|
|
150
|
+
THEN 1 ELSE 0 END) AS found
|
|
136
151
|
FROM "SessionLog" sl
|
|
137
152
|
INNER JOIN "Session" s ON sl."session_id" = s."id"
|
|
138
153
|
WHERE sl."original_strategy" = ${row.original_strategy}
|
|
@@ -142,7 +157,7 @@ let SelectorVerificationJob = class SelectorVerificationJob {
|
|
|
142
157
|
AND s."build_id" IS NOT NULL
|
|
143
158
|
GROUP BY s."build_id"
|
|
144
159
|
`;
|
|
145
|
-
const cleanBuildCount = heals.filter((r) => Number(r.healed) === 0).length;
|
|
160
|
+
const cleanBuildCount = heals.filter((r) => Number(r.healed) === 0 && Number(r.found) === 1).length;
|
|
146
161
|
if (cleanBuildCount >= CLEAN_BUILDS_TO_RESOLVE) {
|
|
147
162
|
yield this.promoteToResolved(row);
|
|
148
163
|
}
|
|
@@ -169,7 +184,7 @@ let SelectorVerificationJob = class SelectorVerificationJob {
|
|
|
169
184
|
return promoted;
|
|
170
185
|
}));
|
|
171
186
|
log.info(`[${row.id}] promoted to resolved (${row.original_strategy}=${row.original_selector})`);
|
|
172
|
-
this.socket.
|
|
187
|
+
void this.socket.emitToDashboardForSelector(SocketEvents_1.SocketEvents.SELECTOR_RESOLVED, this.serialize(updated), keyOfRow(row));
|
|
173
188
|
});
|
|
174
189
|
}
|
|
175
190
|
updateProgress(row, cleanBuildCount) {
|
|
@@ -178,7 +193,7 @@ let SelectorVerificationJob = class SelectorVerificationJob {
|
|
|
178
193
|
where: { id: row.id },
|
|
179
194
|
data: { clean_builds_count: cleanBuildCount, last_event_at: new Date() },
|
|
180
195
|
});
|
|
181
|
-
this.socket.
|
|
196
|
+
void this.socket.emitToDashboardForSelector(SocketEvents_1.SocketEvents.SELECTOR_PROGRESS, this.serialize(updated), keyOfRow(row));
|
|
182
197
|
});
|
|
183
198
|
}
|
|
184
199
|
serialize(row) {
|
|
@@ -79,6 +79,7 @@ const IPluginArgs_1 = require("../interfaces/IPluginArgs");
|
|
|
79
79
|
const config_service_1 = require("../data-service/config-service");
|
|
80
80
|
const PluginContext_1 = require("../PluginContext");
|
|
81
81
|
const SelfHealingSwitch_1 = require("./settings/SelfHealingSwitch");
|
|
82
|
+
const aiEngineSettings_1 = require("./settings/aiEngineSettings");
|
|
82
83
|
const device_store_1 = require("../data-service/device-store");
|
|
83
84
|
const device_utils_1 = require("../device-utils");
|
|
84
85
|
const localDeviceHosts_1 = require("../device-managers/localDeviceHosts");
|
|
@@ -140,6 +141,9 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
140
141
|
// The Settings page's AI self-healing toggle, which the command interceptor
|
|
141
142
|
// reads from memory at every command: load it before a command can arrive.
|
|
142
143
|
yield typedi_1.Container.get(SelfHealingSwitch_1.SelfHealingSwitch).load();
|
|
144
|
+
// The AI engine page's provider, model and base URL, over the startup
|
|
145
|
+
// options applied above (what a cleared value goes back to).
|
|
146
|
+
yield typedi_1.Container.get(aiEngineSettings_1.AiEngineSettings).load();
|
|
143
147
|
this.registerRoutes(expressApp, httpServer, cliArgs, pluginArgs);
|
|
144
148
|
yield this.bootEmulators(pluginArgs);
|
|
145
149
|
this.registerDependenciesInContainer(pluginArgs, cliArgs, nodeId);
|
|
@@ -397,6 +401,7 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
397
401
|
const { bootstrapIdentity } = yield Promise.resolve().then(() => __importStar(require('./identity/bootstrap')));
|
|
398
402
|
yield bootstrapIdentity();
|
|
399
403
|
typedi_1.Container.get(EmailService_1.EmailService).warnIfLogFallbackEnabled();
|
|
404
|
+
typedi_1.Container.get(EmailService_1.EmailService).warnAboutPublicUrl();
|
|
400
405
|
const { startUserSessionCleanupCron } = yield Promise.resolve().then(() => __importStar(require('./identity/sessionCleanupCron')));
|
|
401
406
|
startUserSessionCleanupCron();
|
|
402
407
|
// Hub-issued JWT signing key (REST/MCP tokens, stream tickets) — key
|
|
@@ -445,7 +450,12 @@ let ServerManager = ServerManager_1 = class ServerManager {
|
|
|
445
450
|
});
|
|
446
451
|
}
|
|
447
452
|
registerRoutes(expressApp, httpServer, cliArgs, pluginArgs) {
|
|
448
|
-
expressApp.use('/xenon', (0, app_1.createRouter)(pluginArgs
|
|
453
|
+
expressApp.use('/xenon', (0, app_1.createRouter)(pluginArgs, {
|
|
454
|
+
address: cliArgs.address,
|
|
455
|
+
port: cliArgs.port,
|
|
456
|
+
// Appium serves HTTPS with both of these (base-driver's server()).
|
|
457
|
+
tls: !!(cliArgs.sslCertificatePath && cliArgs.sslKeyPath),
|
|
458
|
+
}));
|
|
449
459
|
// The session gateway goes in front of Appium's routes: Xenon's own
|
|
450
460
|
// /wd-internal calls (with the per-process secret), per-command auth
|
|
451
461
|
// (XENON_REQUIRE_COMMAND_AUTH), then, on a hub, forwarding of the sessions
|
|
@@ -56,8 +56,6 @@ const typedi_1 = require("typedi");
|
|
|
56
56
|
const uuid_1 = require("uuid");
|
|
57
57
|
const lodash_1 = __importDefault(require("lodash"));
|
|
58
58
|
const axios_1 = __importDefault(require("axios"));
|
|
59
|
-
const https_proxy_agent_1 = require("https-proxy-agent");
|
|
60
|
-
const http_proxy_agent_1 = require("http-proxy-agent");
|
|
61
59
|
const logger_1 = __importDefault(require("../logger"));
|
|
62
60
|
const helpers_1 = require("../helpers");
|
|
63
61
|
const InternalHttpClient_1 = require("../InternalHttpClient");
|
|
@@ -69,7 +67,6 @@ const device_utils_1 = require("../device-utils");
|
|
|
69
67
|
const TracingService_1 = require("./TracingService");
|
|
70
68
|
const PortAllocator_1 = require("./PortAllocator");
|
|
71
69
|
const XenonCapabilityManager_1 = require("../XenonCapabilityManager");
|
|
72
|
-
const JwtKeyService_1 = require("./token/JwtKeyService");
|
|
73
70
|
const sessionIdentity_1 = require("./session/sessionIdentity");
|
|
74
71
|
const leaseSessionCaps_1 = require("./lease/leaseSessionCaps");
|
|
75
72
|
const sessionCredentials_1 = require("./session/sessionCredentials");
|
|
@@ -80,6 +77,7 @@ const callerTeamIds_1 = require("./device-access/callerTeamIds");
|
|
|
80
77
|
const queueVisibility_1 = require("./device-access/queueVisibility");
|
|
81
78
|
const appVisibility_1 = require("./device-access/appVisibility");
|
|
82
79
|
const LiveSessionOwners_1 = require("./device-access/LiveSessionOwners");
|
|
80
|
+
const sessionMemory_1 = require("../sessions/sessionMemory");
|
|
83
81
|
const SessionMetricsService_1 = require("./metrics/SessionMetricsService");
|
|
84
82
|
const appCapability_1 = require("./session/appCapability");
|
|
85
83
|
const CircuitBreaker_1 = require("../data-service/CircuitBreaker");
|
|
@@ -100,6 +98,21 @@ const async_lock_1 = __importDefault(require("async-lock"));
|
|
|
100
98
|
const base_driver_1 = require("@appium/base-driver");
|
|
101
99
|
const PhoneNetworkRestore_1 = require("./network/PhoneNetworkRestore");
|
|
102
100
|
const interceptorOptions_1 = require("./interceptor/interceptorOptions");
|
|
101
|
+
/**
|
|
102
|
+
* A session token admits a session only with the `sessions` scope, as a key
|
|
103
|
+
* pair does. Tokens minted before this check carry no `scopes` claim, and
|
|
104
|
+
* POST /auth/token gave one to any credential, a read-only one included:
|
|
105
|
+
* they are refused too.
|
|
106
|
+
*/
|
|
107
|
+
function sessionTokenGrantsSessions(scopes) {
|
|
108
|
+
if (typeof scopes !== 'string')
|
|
109
|
+
return false;
|
|
110
|
+
const owned = scopes.split(',').map((s) => s.trim());
|
|
111
|
+
return owned.includes('sessions') || owned.includes('admin');
|
|
112
|
+
}
|
|
113
|
+
const SESSION_TOKEN_LACKS_SCOPE = 'session rejected: xe:options.sessionToken carries no `sessions` scope; tokens minted by ' +
|
|
114
|
+
'Xenon 2.14 or earlier carry none — mint a new one with POST /xenon/api/auth/token ' +
|
|
115
|
+
'({"audience":"xenon-mcp"}) using a credential that has the `sessions` scope.';
|
|
103
116
|
/**
|
|
104
117
|
* How long the hub waits for a node (or cloud provider) to create a session.
|
|
105
118
|
* A create is sent once (InternalRequestConfig `retry: false`): the node may
|
|
@@ -263,6 +276,7 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
263
276
|
apiKeyId: authResult.apiKeyId,
|
|
264
277
|
userId: authResult.userId,
|
|
265
278
|
appDownload,
|
|
279
|
+
allocatedAt: Date.now(),
|
|
266
280
|
};
|
|
267
281
|
// The grant opens one phone, on this node. A token taken to another node
|
|
268
282
|
// (an emulator's udid repeats across machines), or naming a phone this
|
|
@@ -391,7 +405,7 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
391
405
|
this.logger.debug(`📱 Removing pending session with capability_id: ${allocation.pendingSessionId}`);
|
|
392
406
|
yield (0, pending_sessions_service_1.removePendingSession)(allocation.pendingSessionId);
|
|
393
407
|
if (this.isCreateSessionResponseInternal(session)) {
|
|
394
|
-
yield this.finalizeSession(session, device, caps, driver, remote, allocation.apiKeyId, allocation.userId);
|
|
408
|
+
yield this.finalizeSession(session, device, caps, driver, remote, allocation.apiKeyId, allocation.userId, allocation.allocatedAt);
|
|
395
409
|
}
|
|
396
410
|
else {
|
|
397
411
|
yield this.handleSessionFailure(session, device, remote);
|
|
@@ -505,9 +519,11 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
505
519
|
// took out of `xe:options` (or its `xenon:options` alias): an access-key +
|
|
506
520
|
// token pair, or a session token. Today this is best-effort: if none is
|
|
507
521
|
// supplied we log a warning and let the request through to avoid breaking
|
|
508
|
-
// pre-existing test clients, unless XENON_REQUIRE_SESSION_TOKEN is on.
|
|
509
|
-
//
|
|
510
|
-
//
|
|
522
|
+
// pre-existing test clients, unless XENON_REQUIRE_SESSION_TOKEN is on.
|
|
523
|
+
// Credentials that don't verify — a wrong, revoked or expired key or token,
|
|
524
|
+
// or one whose user is deleted or not ACTIVE — count as none. Credentials
|
|
525
|
+
// that verify without the `sessions` scope are rejected. Respects the
|
|
526
|
+
// global `authDisabled` flag.
|
|
511
527
|
//
|
|
512
528
|
// Returns the caller's { apiKeyId, callerTeamIds } so allocation can filter
|
|
513
529
|
// devices, and app resolution uploaded apps, by team. `apiKeyId` is null
|
|
@@ -549,23 +565,47 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
549
565
|
}
|
|
550
566
|
const { ApiKeyService } = yield Promise.resolve().then(() => __importStar(require('./ApiKeyService')));
|
|
551
567
|
const svc = typedi_1.Container.get(ApiKeyService);
|
|
568
|
+
const { verifyKeyPairCredential, verifySessionTokenCredential, isWrongSessionToken } = yield Promise.resolve().then(() => __importStar(require('../middleware/verifyCredential')));
|
|
552
569
|
// xe:options.{accessKey, token} — the only key-pair credential shape.
|
|
570
|
+
// Checked by REST's rule (verifyKeyPairCredential): a live key whose owner
|
|
571
|
+
// exists and is ACTIVE. The key alone (verifyPair) let a user set to
|
|
572
|
+
// Inactive, or deleted, keep creating sessions as themselves. The owner
|
|
573
|
+
// found here is the one lookup the session's scope and lease use.
|
|
553
574
|
const pair = credentials.pair;
|
|
554
|
-
const
|
|
575
|
+
const verifiedPair = pair ? yield verifyKeyPairCredential(pair.accessKey, pair.token) : null;
|
|
576
|
+
// The session token, verified once for both the gate and attribution, by
|
|
577
|
+
// REST's rule too: a live signature and an ACTIVE subject, looked up now.
|
|
578
|
+
let tokenCheck;
|
|
579
|
+
const verifyToken = (t) => {
|
|
580
|
+
if (!tokenCheck)
|
|
581
|
+
tokenCheck = verifySessionTokenCredential(t);
|
|
582
|
+
return tokenCheck;
|
|
583
|
+
};
|
|
584
|
+
// Both readers below treat any failure as a token that doesn't check out:
|
|
585
|
+
// the gate refuses it as "invalid or expired", attribution ignores it. A
|
|
586
|
+
// check that could not run (the database or the signing key unavailable)
|
|
587
|
+
// is not that: it refuses the create here, as a key whose owner can't be
|
|
588
|
+
// looked up does, rather than run the session with no owner.
|
|
589
|
+
if (!verifiedPair && credentials.sessionToken) {
|
|
590
|
+
yield verifyToken(credentials.sessionToken).catch((err) => {
|
|
591
|
+
if (!isWrongSessionToken(err))
|
|
592
|
+
throw err;
|
|
593
|
+
});
|
|
594
|
+
}
|
|
555
595
|
const { assertSessionTokenGate, sessionTokenGateEnabled } = yield Promise.resolve().then(() => __importStar(require('./sessionTokenGate')));
|
|
556
596
|
try {
|
|
557
597
|
yield assertSessionTokenGate({
|
|
558
598
|
enabled: sessionTokenGateEnabled(),
|
|
559
|
-
hasValidKeyPair: !!
|
|
599
|
+
hasValidKeyPair: !!verifiedPair,
|
|
560
600
|
token: credentials.sessionToken,
|
|
561
|
-
verify:
|
|
601
|
+
verify: verifyToken,
|
|
562
602
|
});
|
|
563
603
|
}
|
|
564
604
|
catch (err) {
|
|
565
605
|
this.logger.error(`❌ ${err.message}`);
|
|
566
606
|
throw new base_driver_1.errors.InvalidArgumentError(err.message);
|
|
567
607
|
}
|
|
568
|
-
if (!
|
|
608
|
+
if (!verifiedPair) {
|
|
569
609
|
// No key pair. An xe:options.sessionToken still identifies the caller,
|
|
570
610
|
// so read it for attribution even when the gate is off — otherwise the
|
|
571
611
|
// ownership guard fails closed on a session whose owner we actually know.
|
|
@@ -573,23 +613,36 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
573
613
|
// The token's teamId claim narrows it the way a key's team does; kept
|
|
574
614
|
// from the one verify rather than verifying again.
|
|
575
615
|
let tokenTeamId = null;
|
|
616
|
+
let tokenScopes;
|
|
617
|
+
let tokenOwner;
|
|
576
618
|
const identity = yield (0, sessionIdentity_1.resolveSessionIdentity)({
|
|
577
619
|
row: null,
|
|
578
620
|
sessionToken: credentials.sessionToken,
|
|
579
621
|
verify: (t) => __awaiter(this, void 0, void 0, function* () {
|
|
580
|
-
const payload = yield
|
|
581
|
-
audience: 'xenon-session',
|
|
582
|
-
});
|
|
622
|
+
const { payload, user } = yield verifyToken(t);
|
|
583
623
|
tokenTeamId = typeof (payload === null || payload === void 0 ? void 0 : payload.teamId) === 'string' ? payload.teamId : null;
|
|
624
|
+
tokenScopes = payload === null || payload === void 0 ? void 0 : payload.scopes;
|
|
625
|
+
tokenOwner = user;
|
|
584
626
|
return payload;
|
|
585
627
|
}),
|
|
586
628
|
});
|
|
629
|
+
// A token that names its owner may create sessions only with the
|
|
630
|
+
// `sessions` scope, as a key pair may: refused, like a key without it.
|
|
631
|
+
if (identity.userId && !sessionTokenGrantsSessions(tokenScopes)) {
|
|
632
|
+
this.logger.error('Rejecting session: xe:options.sessionToken lacks the `sessions` scope');
|
|
633
|
+
throw new base_driver_1.errors.InvalidArgumentError(SESSION_TOKEN_LACKS_SCOPE);
|
|
634
|
+
}
|
|
587
635
|
if (!identity.userId) {
|
|
588
636
|
this.logger.warn('Session created without valid credentials. Pass `xe:options.accessKey` + ' +
|
|
589
637
|
'`xe:options.token`, or `xe:options.sessionToken`.');
|
|
590
638
|
}
|
|
591
|
-
const leaseAccess = this.leaseAccessFor(identity.userId
|
|
592
|
-
? {
|
|
639
|
+
const leaseAccess = this.leaseAccessFor(identity.userId && tokenOwner
|
|
640
|
+
? {
|
|
641
|
+
kind: 'session-token',
|
|
642
|
+
scopes: String(tokenScopes),
|
|
643
|
+
user: tokenOwner,
|
|
644
|
+
teamId: tokenTeamId,
|
|
645
|
+
}
|
|
593
646
|
: { kind: 'none' });
|
|
594
647
|
// A token's user is scoped as REST scopes them. No credentials at all
|
|
595
648
|
// stays unscoped (the back-compat path), with no lookup.
|
|
@@ -599,6 +652,7 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
599
652
|
teamId: identity.userId ? tokenTeamId : null,
|
|
600
653
|
} });
|
|
601
654
|
}
|
|
655
|
+
const { row, user: owner } = verifiedPair;
|
|
602
656
|
if (!svc.hasScope(row, ['sessions'])) {
|
|
603
657
|
this.logger.error('Rejecting session: credentials lack the `sessions` scope');
|
|
604
658
|
throw new base_driver_1.errors.InvalidArgumentError('credentials are invalid, revoked, or lack the `sessions` scope');
|
|
@@ -607,7 +661,7 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
607
661
|
const leaseAccess = this.leaseAccessFor({
|
|
608
662
|
kind: 'api-key',
|
|
609
663
|
scopes: row.scopes,
|
|
610
|
-
|
|
664
|
+
user: owner,
|
|
611
665
|
teamId: (_a = row.teamId) !== null && _a !== void 0 ? _a : null,
|
|
612
666
|
});
|
|
613
667
|
// The phones and apps this session may use: REST's rule
|
|
@@ -642,7 +696,8 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
642
696
|
// How a session that names a lease is judged, looked up at most once and
|
|
643
697
|
// only when asked. Who may override follows canOverrideLease; which phones
|
|
644
698
|
// it can see follows REST's computeTeamIds. Both need the owner's live role,
|
|
645
|
-
// as authMiddleware reads it for every REST request
|
|
699
|
+
// as authMiddleware reads it for every REST request: the owner the
|
|
700
|
+
// credential check looked up a moment ago, ACTIVE then.
|
|
646
701
|
leaseAccessFor(credential) {
|
|
647
702
|
let looked;
|
|
648
703
|
return () => {
|
|
@@ -659,24 +714,24 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
659
714
|
if (credential.kind === 'none') {
|
|
660
715
|
return { canOverride: (0, leaseOverride_1.canOverrideLease)({ kind: 'none' }), teamIds: undefined };
|
|
661
716
|
}
|
|
717
|
+
const { user } = credential;
|
|
662
718
|
try {
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
const role = (user === null || user === void 0 ? void 0 : user.role) === 'SUPER_ADMIN' || (user === null || user === void 0 ? void 0 : user.role) === 'ADMIN' ? user.role : 'MEMBER';
|
|
719
|
+
const canOverride = (0, leaseOverride_1.canOverrideLease)({
|
|
720
|
+
kind: credential.kind,
|
|
721
|
+
scopes: credential.scopes,
|
|
722
|
+
user,
|
|
723
|
+
});
|
|
724
|
+
const role = user.role === 'SUPER_ADMIN' || user.role === 'ADMIN' ? user.role : 'MEMBER';
|
|
670
725
|
const teamIds = yield (0, callerTeamIds_1.computeTeamIds)({
|
|
671
726
|
role,
|
|
672
|
-
userId:
|
|
727
|
+
userId: user.id,
|
|
673
728
|
apiKeyTeamId: credential.teamId,
|
|
674
729
|
});
|
|
675
730
|
return { canOverride, teamIds };
|
|
676
731
|
}
|
|
677
732
|
catch (err) {
|
|
678
733
|
// Fail closed: no override, and the shared pool only.
|
|
679
|
-
this.logger.warn(`Could not look up how user ${
|
|
734
|
+
this.logger.warn(`Could not look up how user ${user.id} may use a lease: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
680
735
|
return { canOverride: false, teamIds: [] };
|
|
681
736
|
}
|
|
682
737
|
});
|
|
@@ -790,8 +845,8 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
790
845
|
}
|
|
791
846
|
}
|
|
792
847
|
finalizeSession(session_1, device_1, caps_1, driver_1, isRemote_1) {
|
|
793
|
-
return __awaiter(this, arguments, void 0, function* (session, device, caps, driver, isRemote, apiKeyId = null, userId = null) {
|
|
794
|
-
var _a;
|
|
848
|
+
return __awaiter(this, arguments, void 0, function* (session, device, caps, driver, isRemote, apiKeyId = null, userId = null, allocatedAt) {
|
|
849
|
+
var _a, _b;
|
|
795
850
|
const sessionId = session.value[0];
|
|
796
851
|
const sessionResponse = session.value[1];
|
|
797
852
|
const requestedCaps = Object.assign({}, ((_a = caps.firstMatch) === null || _a === void 0 ? void 0 : _a[0]) || {}, caps.alwaysMatch || {});
|
|
@@ -808,6 +863,23 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
808
863
|
'xenon.udid': device.udid,
|
|
809
864
|
});
|
|
810
865
|
}
|
|
866
|
+
try {
|
|
867
|
+
yield this.registerSession(sessionId, sessionResponse, device, xenonCapabilities, driver, isRemote, apiKeyId, userId, allocatedAt);
|
|
868
|
+
}
|
|
869
|
+
catch (err) {
|
|
870
|
+
// The create fails, so no ending of the session will come to end it.
|
|
871
|
+
tracingService.endSessionSpan(sessionId, {
|
|
872
|
+
failed: true,
|
|
873
|
+
reason: `Xenon could not finish creating the session: ${(_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err}`,
|
|
874
|
+
});
|
|
875
|
+
throw err;
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
/** The rest of finalizeSession, once the session's span has started. */
|
|
880
|
+
registerSession(sessionId, sessionResponse, device, xenonCapabilities, driver, isRemote, apiKeyId, userId, allocatedAt) {
|
|
881
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
882
|
+
const context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
811
883
|
// The session takes over the pending claim its phone was allocated with.
|
|
812
884
|
yield (0, device_service_1.claimDeviceForSession)(device, sessionId, {
|
|
813
885
|
session_id: sessionId,
|
|
@@ -826,6 +898,7 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
826
898
|
const sessionInstance = this.createSessionInstance(sessionId, freshDevice, sessionResponse, xenonCapabilities, driver, webDriverUrl);
|
|
827
899
|
sessionInstance.apiKeyId = apiKeyId;
|
|
828
900
|
sessionInstance.userId = userId;
|
|
901
|
+
sessionInstance.allocatedAt = allocatedAt;
|
|
829
902
|
// A session this server drives: its owner is known while it runs, row or
|
|
830
903
|
// not (a node writes none for the hub's sessions; LiveSessionOwners).
|
|
831
904
|
if (sessionInstance instanceof LocalSession_1.LocalSession) {
|
|
@@ -1005,19 +1078,8 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
1005
1078
|
timeout: exports.REMOTE_CREATE_TIMEOUT_MS,
|
|
1006
1079
|
retry: false,
|
|
1007
1080
|
};
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
const proxyUrl = typeof context.pluginArgs.proxy === 'string'
|
|
1011
|
-
? context.pluginArgs.proxy
|
|
1012
|
-
: `http://${context.pluginArgs.proxy.host}:${context.pluginArgs.proxy.port}`;
|
|
1013
|
-
config.httpsAgent = new https_proxy_agent_1.HttpsProxyAgent(proxyUrl, {
|
|
1014
|
-
rejectUnauthorized: context.pluginArgs.tlsRejectUnauthorized,
|
|
1015
|
-
});
|
|
1016
|
-
config.httpAgent = new http_proxy_agent_1.HttpProxyAgent(proxyUrl, {
|
|
1017
|
-
rejectUnauthorized: context.pluginArgs.tlsRejectUnauthorized,
|
|
1018
|
-
});
|
|
1019
|
-
config.proxy = false;
|
|
1020
|
-
}
|
|
1081
|
+
// Its proxy is InternalHttpClient's, by Xenon's one rule: the `proxy`
|
|
1082
|
+
// option, else the environment's (helpers/outboundProxy.ts).
|
|
1021
1083
|
const createdSession = yield this.invokeSessionRequest(config, context.pluginArgs.tlsRejectUnauthorized);
|
|
1022
1084
|
if (createdSession instanceof Error) {
|
|
1023
1085
|
return createdSession;
|
|
@@ -1148,6 +1210,13 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
1148
1210
|
finally {
|
|
1149
1211
|
if (sessionId) {
|
|
1150
1212
|
typedi_1.Container.get(LiveSessionOwners_1.LiveSessionOwners).forget(sessionId);
|
|
1213
|
+
(0, sessionMemory_1.forgetSessionMemory)(sessionId);
|
|
1214
|
+
// Whether or not SESSION_MANAGER holds the session (a local one with
|
|
1215
|
+
// the dashboard off), and once: a second delete finds no span.
|
|
1216
|
+
typedi_1.Container.get(TracingService_1.TracingService).endSessionSpan(sessionId, {
|
|
1217
|
+
failed: status === SessionStatus_1.SessionStatus.FAILED,
|
|
1218
|
+
reason,
|
|
1219
|
+
});
|
|
1151
1220
|
// Before the lock's "still in memory?" check, which a dashboard-off
|
|
1152
1221
|
// node fails. On a node this ends the figures it holds for its hub;
|
|
1153
1222
|
// on a hub it collects a node session's last ones. Idempotent.
|
|
@@ -1181,8 +1250,11 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
1181
1250
|
logger_1.default.warn(`Port release failed: ${err.message}`);
|
|
1182
1251
|
}
|
|
1183
1252
|
}
|
|
1184
|
-
// 1. iOS Profiling Archival
|
|
1185
|
-
|
|
1253
|
+
// 1. iOS Profiling Archival. Only on an iPhone, the one place
|
|
1254
|
+
// EventManager.onSessionStarted starts it. A simulator's driver refuses
|
|
1255
|
+
// the stop without relaxed security, and the refused stop counted as a
|
|
1256
|
+
// failed command of the session: every simulator session ended failed.
|
|
1257
|
+
if (device && ((_a = device.platform) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'ios' && device.realDevice === true) {
|
|
1186
1258
|
this.logger.info(`[${sessionId}] Stopping iOS profiling for asset archival`);
|
|
1187
1259
|
try {
|
|
1188
1260
|
const traceBase64 = yield session.stopPerformanceRecording();
|
|
@@ -1263,6 +1335,8 @@ let SessionLifecycleService = class SessionLifecycleService {
|
|
|
1263
1335
|
}
|
|
1264
1336
|
SessionManager_1.SESSION_MANAGER.removeSession(sessionId);
|
|
1265
1337
|
typedi_1.Container.get(LiveSessionOwners_1.LiveSessionOwners).forget(sessionId);
|
|
1338
|
+
(0, sessionMemory_1.forgetSessionMemory)(sessionId);
|
|
1339
|
+
typedi_1.Container.get(TracingService_1.TracingService).endSessionSpan(sessionId, { failed: true, reason });
|
|
1266
1340
|
yield typedi_1.Container.get(SessionMetricsService_1.SessionMetricsService).stop(sessionId);
|
|
1267
1341
|
}));
|
|
1268
1342
|
});
|
|
@@ -15,6 +15,7 @@ const typedi_1 = require("typedi");
|
|
|
15
15
|
const logger_1 = __importDefault(require("../logger"));
|
|
16
16
|
const config_1 = require("../config");
|
|
17
17
|
const SocketEvents_1 = require("../enums/SocketEvents");
|
|
18
|
+
const outboundProxy_1 = require("../helpers/outboundProxy");
|
|
18
19
|
let SocketClient = class SocketClient {
|
|
19
20
|
constructor() {
|
|
20
21
|
this.socket = null;
|
|
@@ -46,6 +47,16 @@ let SocketClient = class SocketClient {
|
|
|
46
47
|
reconnectionDelay: 1000,
|
|
47
48
|
reconnectionDelayMax: 5000,
|
|
48
49
|
auth: socketAuth,
|
|
50
|
+
// The proxy the node's other calls to its hub take (its phone reports,
|
|
51
|
+
// its JWKS fetch); it used to go straight to the hub whatever the
|
|
52
|
+
// environment said. Polling goes as plain requests, as axios sends
|
|
53
|
+
// them; only the WebSocket upgrade needs a CONNECT tunnel, and when the
|
|
54
|
+
// proxy refuses one (a stock Squid allows port 443 only) the
|
|
55
|
+
// connection stays on polling.
|
|
56
|
+
transportOptions: {
|
|
57
|
+
polling: { agent: (0, outboundProxy_1.proxyAgentFor)(normalizedHubUrl) },
|
|
58
|
+
websocket: { agent: (0, outboundProxy_1.socketProxyAgentFor)(normalizedHubUrl) },
|
|
59
|
+
},
|
|
49
60
|
});
|
|
50
61
|
this.socket.on('connect', () => {
|
|
51
62
|
var _a, _b, _c;
|