@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
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.AiEngineSettings = exports.AI_ENGINE_SETTINGS = exports.AI_PROVIDERS = void 0;
|
|
22
|
+
exports.pickAiEngine = pickAiEngine;
|
|
23
|
+
exports.effectiveAiEngine = effectiveAiEngine;
|
|
24
|
+
exports.validateAiEngineUpdate = validateAiEngineUpdate;
|
|
25
|
+
exports.aiEngineUpdateOf = aiEngineUpdateOf;
|
|
26
|
+
const typedi_1 = require("typedi");
|
|
27
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
28
|
+
const config_1 = require("../../config");
|
|
29
|
+
const web_config_service_1 = require("../../data-service/web-config-service");
|
|
30
|
+
/**
|
|
31
|
+
* The AI engine page's settings: which provider answers the AI calls (failure
|
|
32
|
+
* analysis, the visual and LLM healing tiers, visual assertions), and the model
|
|
33
|
+
* and address it uses.
|
|
34
|
+
*
|
|
35
|
+
* They follow the rule every lab setting does (labSettings.ts): a value saved
|
|
36
|
+
* in the dashboard (`WebConfig`, written by `POST /config`), else the plugin
|
|
37
|
+
* option or environment variable the server was started with, else the
|
|
38
|
+
* default. The result is written to `config`, which every AI call reads, at
|
|
39
|
+
* boot and on each save, so a change applies to the next call without a
|
|
40
|
+
* restart. Through 2.14 `POST /config` changed `config` and saved nothing: a
|
|
41
|
+
* restart went back to the provider the server was started with.
|
|
42
|
+
*
|
|
43
|
+
* API keys are not among them. They come from the server's environment only,
|
|
44
|
+
* are never saved, and are never sent to the dashboard.
|
|
45
|
+
*/
|
|
46
|
+
exports.AI_PROVIDERS = ['gemini', 'openai', 'anthropic', 'ollama'];
|
|
47
|
+
/** The settings decided here, by their names in `config`, `WebConfig` and `/config`. */
|
|
48
|
+
exports.AI_ENGINE_SETTINGS = [
|
|
49
|
+
'aiProvider',
|
|
50
|
+
'aiModel',
|
|
51
|
+
'aiBaseUrl',
|
|
52
|
+
'geminiModel',
|
|
53
|
+
'openaiModel',
|
|
54
|
+
'anthropicModel',
|
|
55
|
+
'ollamaModel',
|
|
56
|
+
];
|
|
57
|
+
const MODEL_SETTINGS = [
|
|
58
|
+
'aiModel',
|
|
59
|
+
'geminiModel',
|
|
60
|
+
'openaiModel',
|
|
61
|
+
'anthropicModel',
|
|
62
|
+
'ollamaModel',
|
|
63
|
+
];
|
|
64
|
+
const DEFAULT_PROVIDER = 'gemini';
|
|
65
|
+
const MODEL_MAX_LENGTH = 200;
|
|
66
|
+
const BASE_URL_MAX_LENGTH = 2048;
|
|
67
|
+
const isProvider = (v) => typeof v === 'string' && exports.AI_PROVIDERS.includes(v);
|
|
68
|
+
const isNonEmpty = (v) => typeof v === 'string' && v.trim() !== '';
|
|
69
|
+
/** The AI engine settings in `source` (the running config, the saved rows), and nothing else. */
|
|
70
|
+
function pickAiEngine(source) {
|
|
71
|
+
const out = {};
|
|
72
|
+
for (const name of exports.AI_ENGINE_SETTINGS) {
|
|
73
|
+
if (typeof source[name] === 'string')
|
|
74
|
+
out[name] = source[name];
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The settings in effect, field by field: the saved value when it can work,
|
|
80
|
+
* else the one the server was started with, else the default (gemini; a model
|
|
81
|
+
* or base URL has none, and the provider's own then applies). An empty saved
|
|
82
|
+
* value is the person clearing it. The started-with provider is kept as it
|
|
83
|
+
* is, even one Xenon doesn't know: falling back to another provider would
|
|
84
|
+
* send screenshots somewhere the server's owner didn't choose. AIService then
|
|
85
|
+
* runs with no provider, and says so.
|
|
86
|
+
*/
|
|
87
|
+
function effectiveAiEngine(startup, saved) {
|
|
88
|
+
const out = {
|
|
89
|
+
aiProvider: isProvider(saved.aiProvider)
|
|
90
|
+
? saved.aiProvider
|
|
91
|
+
: isNonEmpty(startup.aiProvider)
|
|
92
|
+
? startup.aiProvider
|
|
93
|
+
: DEFAULT_PROVIDER,
|
|
94
|
+
};
|
|
95
|
+
for (const name of exports.AI_ENGINE_SETTINGS) {
|
|
96
|
+
if (name === 'aiProvider')
|
|
97
|
+
continue;
|
|
98
|
+
const mine = saved[name];
|
|
99
|
+
out[name] = isNonEmpty(mine)
|
|
100
|
+
? mine.trim()
|
|
101
|
+
: isNonEmpty(startup[name])
|
|
102
|
+
? startup[name]
|
|
103
|
+
: undefined;
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/** Why `url` can't be saved as the base URL, or null. */
|
|
108
|
+
function baseUrlProblem(url) {
|
|
109
|
+
const why = "aiBaseUrl must be an http or https address with no user name, password or query (the API key comes from the server's environment), or empty to use the server's own.";
|
|
110
|
+
if (url.length > BASE_URL_MAX_LENGTH)
|
|
111
|
+
return why;
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = new URL(url);
|
|
115
|
+
}
|
|
116
|
+
catch (_a) {
|
|
117
|
+
return why;
|
|
118
|
+
}
|
|
119
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
120
|
+
return why;
|
|
121
|
+
// Anything that could carry a credential would be one saved in the database.
|
|
122
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash)
|
|
123
|
+
return why;
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* What is wrong with the AI engine fields of a settings update, or null.
|
|
128
|
+
* Checked before anything is saved: a saved value outlives the restart that
|
|
129
|
+
* used to undo it. Each may be empty, to go back to what the server was
|
|
130
|
+
* started with. Fields not sent are not checked.
|
|
131
|
+
*/
|
|
132
|
+
function validateAiEngineUpdate(body) {
|
|
133
|
+
const has = (key) => body[key] !== undefined;
|
|
134
|
+
if (has('aiProvider') && !(body.aiProvider === '' || isProvider(body.aiProvider))) {
|
|
135
|
+
return {
|
|
136
|
+
field: 'aiProvider',
|
|
137
|
+
message: `aiProvider must be one of ${exports.AI_PROVIDERS.join(', ')}, or empty to use the server's own.`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
for (const field of MODEL_SETTINGS) {
|
|
141
|
+
if (!has(field))
|
|
142
|
+
continue;
|
|
143
|
+
const value = body[field];
|
|
144
|
+
const name = typeof value === 'string' ? value.trim() : null;
|
|
145
|
+
if (name === null || name.length > MODEL_MAX_LENGTH || /[\s\u0000-\u001f\u007f]/.test(name)) {
|
|
146
|
+
return {
|
|
147
|
+
field,
|
|
148
|
+
message: `${field} must be a model name with no spaces, of at most ${MODEL_MAX_LENGTH} characters, or empty to use the server's own.`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (has('aiBaseUrl')) {
|
|
153
|
+
const value = body.aiBaseUrl;
|
|
154
|
+
const problem = typeof value !== 'string'
|
|
155
|
+
? 'aiBaseUrl must be text.'
|
|
156
|
+
: value.trim() === ''
|
|
157
|
+
? null
|
|
158
|
+
: baseUrlProblem(value.trim());
|
|
159
|
+
if (problem)
|
|
160
|
+
return { field: 'aiBaseUrl', message: problem };
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
/** The AI engine fields of a checked update, as they are saved: names trimmed. */
|
|
165
|
+
function aiEngineUpdateOf(body) {
|
|
166
|
+
const picked = pickAiEngine(body);
|
|
167
|
+
for (const name of Object.keys(picked))
|
|
168
|
+
picked[name] = picked[name].trim();
|
|
169
|
+
return picked;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Keeps `config`'s AI engine settings at the values in effect. Loaded once at
|
|
173
|
+
* boot, after the startup options were applied and the database is ready, and
|
|
174
|
+
* told of each save by `POST /config`.
|
|
175
|
+
*/
|
|
176
|
+
let AiEngineSettings = class AiEngineSettings {
|
|
177
|
+
constructor() {
|
|
178
|
+
/** What the dashboard saved. */
|
|
179
|
+
this.saved = {};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Reads the saved settings and applies them. A database that can't be read
|
|
183
|
+
* leaves the startup settings in charge, as it does for the other settings.
|
|
184
|
+
*/
|
|
185
|
+
load() {
|
|
186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
this.startedWith();
|
|
188
|
+
try {
|
|
189
|
+
this.saved = pickAiEngine((yield typedi_1.Container.get(web_config_service_1.WebConfigService).getConfig()));
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
this.saved = {};
|
|
193
|
+
logger_1.default.warn(`Could not read the saved AI engine settings, using the startup options: ${err === null || err === void 0 ? void 0 : err.message}`);
|
|
194
|
+
}
|
|
195
|
+
this.apply();
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/** What `POST /config` just saved, in force for the next AI call. */
|
|
199
|
+
noteSaved(update) {
|
|
200
|
+
this.startedWith();
|
|
201
|
+
this.saved = Object.assign(Object.assign({}, this.saved), update);
|
|
202
|
+
this.apply();
|
|
203
|
+
}
|
|
204
|
+
startedWith() {
|
|
205
|
+
if (!this.startup)
|
|
206
|
+
this.startup = pickAiEngine(config_1.config);
|
|
207
|
+
return this.startup;
|
|
208
|
+
}
|
|
209
|
+
apply() {
|
|
210
|
+
const before = config_1.config.aiProvider;
|
|
211
|
+
(0, config_1.updateConfig)(effectiveAiEngine(this.startedWith(), this.saved));
|
|
212
|
+
if (config_1.config.aiProvider !== before)
|
|
213
|
+
logger_1.default.info(`AI provider: ${config_1.config.aiProvider}`);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
exports.AiEngineSettings = AiEngineSettings;
|
|
217
|
+
exports.AiEngineSettings = AiEngineSettings = __decorate([
|
|
218
|
+
(0, typedi_1.Service)()
|
|
219
|
+
], AiEngineSettings);
|
|
@@ -51,7 +51,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
51
51
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
52
52
|
};
|
|
53
53
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
-
exports.JwtKeyService = void 0;
|
|
54
|
+
exports.JwtKeyService = exports.JWT_CLOCK_TOLERANCE_SEC = void 0;
|
|
55
55
|
const typedi_1 = require("typedi");
|
|
56
56
|
const jose = __importStar(require("jose"));
|
|
57
57
|
const crypto_1 = require("crypto");
|
|
@@ -59,6 +59,12 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
59
59
|
const path_1 = __importDefault(require("path"));
|
|
60
60
|
const KEY_FILE = 'xenon-jwt-private.pem';
|
|
61
61
|
const ISSUER = process.env.XENON_JWT_ISSUER || 'xenon-hub';
|
|
62
|
+
/**
|
|
63
|
+
* How long past `exp` verify() still takes a token (spec §7.1: ±60 s skew,
|
|
64
|
+
* mirrors appium-mcp-auth). A dashboard socket on a bearer token closes then,
|
|
65
|
+
* when REST stops taking it (SocketServer).
|
|
66
|
+
*/
|
|
67
|
+
exports.JWT_CLOCK_TOLERANCE_SEC = 60;
|
|
62
68
|
/**
|
|
63
69
|
* RS256 signing key for hub-issued JWTs (REST/MCP tokens, stream tickets).
|
|
64
70
|
* Key material lives on disk (0600), never in the database. `kid` is derived
|
|
@@ -119,7 +125,7 @@ let JwtKeyService = class JwtKeyService {
|
|
|
119
125
|
const { payload } = yield jose.jwtVerify(token, this.publicKey, {
|
|
120
126
|
issuer: ISSUER,
|
|
121
127
|
audience: opts.audience,
|
|
122
|
-
clockTolerance:
|
|
128
|
+
clockTolerance: exports.JWT_CLOCK_TOLERANCE_SEC,
|
|
123
129
|
});
|
|
124
130
|
return payload;
|
|
125
131
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_MCP_TOKEN_TTL_SEC = void 0;
|
|
7
|
+
exports.mcpTokenTtlSec = mcpTokenTtlSec;
|
|
8
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
9
|
+
exports.DEFAULT_MCP_TOKEN_TTL_SEC = 86400;
|
|
10
|
+
/**
|
|
11
|
+
* The lifetime of the xenon-mcp tokens and session tokens POST /auth/token
|
|
12
|
+
* mints, in seconds: XENON_MCP_TOKEN_TTL_SEC, a whole number above 0, else a
|
|
13
|
+
* day. Through 2.15 any other value (`abc`) gave every such token a NaN
|
|
14
|
+
* lifetime; it now warns and keeps the default.
|
|
15
|
+
*/
|
|
16
|
+
function mcpTokenTtlSec(raw = process.env.XENON_MCP_TOKEN_TTL_SEC, warn = (message) => logger_1.default.warn(message)) {
|
|
17
|
+
var _a;
|
|
18
|
+
const text = (_a = raw === null || raw === void 0 ? void 0 : raw.trim()) !== null && _a !== void 0 ? _a : '';
|
|
19
|
+
if (text === '')
|
|
20
|
+
return exports.DEFAULT_MCP_TOKEN_TTL_SEC;
|
|
21
|
+
const seconds = /^\d+$/.test(text) ? Number(text) : NaN;
|
|
22
|
+
if (Number.isSafeInteger(seconds) && seconds > 0)
|
|
23
|
+
return seconds;
|
|
24
|
+
warn(`XENON_MCP_TOKEN_TTL_SEC is "${raw}", not a whole number of seconds above 0. ` +
|
|
25
|
+
`The tokens it sets the lifetime of last ${exports.DEFAULT_MCP_TOKEN_TTL_SEC} s, the default.`);
|
|
26
|
+
return exports.DEFAULT_MCP_TOKEN_TTL_SEC;
|
|
27
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Nothing a credential mints outlives it.
|
|
4
|
+
*
|
|
5
|
+
* `credentialExpiresAt` is req.auth.credentialExpiresAt: when the credential
|
|
6
|
+
* making the request stops working (a Bearer token's `exp`, an API key's
|
|
7
|
+
* `expiresAt`), as epoch ms, or undefined for one that doesn't expire (a key
|
|
8
|
+
* without an expiry, a dashboard sign-in, which renews itself).
|
|
9
|
+
*
|
|
10
|
+
* Through 2.14 a 1-hour Bearer token could make itself an API key that never
|
|
11
|
+
* expired (POST /profile/tokens, POST /apikeys), or mint a fresh 1-hour token
|
|
12
|
+
* before it expired, and so on for ever (POST /auth/token).
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.mintedLifetimeSec = mintedLifetimeSec;
|
|
16
|
+
exports.keyExpiryWithin = keyExpiryWithin;
|
|
17
|
+
/**
|
|
18
|
+
* A minted token's lifetime in seconds: `defaultSec`, cut to what is left of
|
|
19
|
+
* the credential minting it, less a second so the minted token's `exp`
|
|
20
|
+
* can't round past it. Throws when nothing is left.
|
|
21
|
+
*/
|
|
22
|
+
function mintedLifetimeSec(defaultSec, credentialExpiresAt) {
|
|
23
|
+
if (credentialExpiresAt === undefined)
|
|
24
|
+
return defaultSec;
|
|
25
|
+
const left = Math.floor((credentialExpiresAt - Date.now()) / 1000) - 1;
|
|
26
|
+
if (left < 1)
|
|
27
|
+
throw new Error('the credential used to mint this token has expired');
|
|
28
|
+
return Math.min(defaultSec, left);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* An API key's expiry, made with a credential that may itself expire: the
|
|
32
|
+
* expiry asked for, or the credential's when none was asked for; an error
|
|
33
|
+
* when the one asked for is later than the credential's, or the credential
|
|
34
|
+
* has already expired.
|
|
35
|
+
*/
|
|
36
|
+
function keyExpiryWithin(requested, credentialExpiresAt) {
|
|
37
|
+
if (credentialExpiresAt === undefined)
|
|
38
|
+
return { expiresAt: requested };
|
|
39
|
+
// Token verification allows 60 s of clock skew, so a request can arrive on
|
|
40
|
+
// a credential just past its exp: it must not make a key already expired.
|
|
41
|
+
if (credentialExpiresAt <= Date.now()) {
|
|
42
|
+
return { error: 'the credential you are using has expired' };
|
|
43
|
+
}
|
|
44
|
+
if (requested && requested.getTime() > credentialExpiresAt) {
|
|
45
|
+
return {
|
|
46
|
+
error: 'expiresAt can be no later than the credential you are using, which expires at ' +
|
|
47
|
+
new Date(credentialExpiresAt).toISOString(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { expiresAt: requested !== null && requested !== void 0 ? requested : new Date(credentialExpiresAt) };
|
|
51
|
+
}
|
|
@@ -109,6 +109,26 @@ class LocalSession extends RemoteSession_1.RemoteSession {
|
|
|
109
109
|
};
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* The device log the session's driver captures (XCUITest's
|
|
114
|
+
* `logs.syslog`), for the session's Device logs (SessionDeviceLogs).
|
|
115
|
+
* Undefined for a driver that keeps none, or a session that skips log
|
|
116
|
+
* capture.
|
|
117
|
+
*/
|
|
118
|
+
deviceLog() {
|
|
119
|
+
var _a, _b, _c, _d, _e, _f;
|
|
120
|
+
const sessionDriver = ((_c = (_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.sessions) === null || _b === void 0 ? void 0 : _b[this.sessionId]) === null || _c === void 0 ? void 0 : _c.proxydriver) ||
|
|
121
|
+
((_e = (_d = this.driver) === null || _d === void 0 ? void 0 : _d.sessions) === null || _e === void 0 ? void 0 : _e[this.sessionId]);
|
|
122
|
+
return (_f = sessionDriver === null || sessionDriver === void 0 ? void 0 : sessionDriver.logs) === null || _f === void 0 ? void 0 : _f.syslog;
|
|
123
|
+
}
|
|
124
|
+
/** The bundle id of the app under test, as the session's driver has it (XCUITest's `opts.bundleId`). */
|
|
125
|
+
appUnderTest() {
|
|
126
|
+
var _a, _b, _c, _d, _e, _f;
|
|
127
|
+
const sessionDriver = ((_c = (_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.sessions) === null || _b === void 0 ? void 0 : _b[this.sessionId]) === null || _c === void 0 ? void 0 : _c.proxydriver) ||
|
|
128
|
+
((_e = (_d = this.driver) === null || _d === void 0 ? void 0 : _d.sessions) === null || _e === void 0 ? void 0 : _e[this.sessionId]);
|
|
129
|
+
const bundleId = (_f = sessionDriver === null || sessionDriver === void 0 ? void 0 : sessionDriver.opts) === null || _f === void 0 ? void 0 : _f.bundleId;
|
|
130
|
+
return typeof bundleId === 'string' && bundleId ? bundleId : undefined;
|
|
131
|
+
}
|
|
112
132
|
/**
|
|
113
133
|
* A local session's driver is in this process, so ask it directly instead of
|
|
114
134
|
* looping back over HTTP. The loopback works, but it re-enters the Appium
|
|
@@ -246,7 +266,7 @@ class LocalSession extends RemoteSession_1.RemoteSession {
|
|
|
246
266
|
// Override to use proper Appium URL for video commands
|
|
247
267
|
startVideoRecording(options, driverOverride) {
|
|
248
268
|
return __awaiter(this, void 0, void 0, function* () {
|
|
249
|
-
var _a, _b, _c;
|
|
269
|
+
var _a, _b, _c, _d;
|
|
250
270
|
logger_1.default.info(`[LocalSession] Starting video recording for session ${this.sessionId}`);
|
|
251
271
|
// Principal Intelligence: For local sessions, try to call the driver directly.
|
|
252
272
|
// We try driverOverride first (e.g. from onUnexpectedShutdown), then search the session map, then this.driver.
|
|
@@ -291,6 +311,14 @@ class LocalSession extends RemoteSession_1.RemoteSession {
|
|
|
291
311
|
const result = yield typedi_1.Container.get(AndroidStreamService_1.default).startStream(device.udid);
|
|
292
312
|
mjpegPort = result.mjpegPort;
|
|
293
313
|
}
|
|
314
|
+
else if (device.platform === 'ios' && !device.realDevice) {
|
|
315
|
+
// A simulator's WebDriverAgent is the driver's, and serves the picture
|
|
316
|
+
// on this Mac at the session's mjpegServerPort. The stream service is
|
|
317
|
+
// an iPhone's (go-ios, iproxy over USB): on a simulator it forwarded
|
|
318
|
+
// nothing, and no video was written.
|
|
319
|
+
mjpegPort =
|
|
320
|
+
Number((_d = this.getCapabilities()) === null || _d === void 0 ? void 0 : _d.mjpegServerPort) || device.mjpegServerPort || undefined;
|
|
321
|
+
}
|
|
294
322
|
else if (device.platform === 'ios') {
|
|
295
323
|
const result = yield typedi_1.Container.get(IOSStreamService_1.default).startStream(device.udid);
|
|
296
324
|
mjpegPort = result.mjpegPort;
|
|
@@ -18,6 +18,7 @@ const typedi_1 = require("typedi");
|
|
|
18
18
|
const logger_1 = __importDefault(require("../logger"));
|
|
19
19
|
const SessionType_1 = __importDefault(require("../enums/SessionType"));
|
|
20
20
|
const hubSessionToken_1 = require("../gateway/hubSessionToken");
|
|
21
|
+
const outboundProxy_1 = require("../helpers/outboundProxy");
|
|
21
22
|
const nodeMetrics_1 = require("../services/metrics/nodeMetrics");
|
|
22
23
|
const nodeSessionStatus_1 = require("../gateway/nodeSessionStatus");
|
|
23
24
|
const XenonSession_1 = require("./XenonSession");
|
|
@@ -52,7 +53,8 @@ class RemoteSession extends XenonSession_1.XenonSession {
|
|
|
52
53
|
return __awaiter(this, void 0, void 0, function* () {
|
|
53
54
|
var _a, _b;
|
|
54
55
|
const options = yield this.callOptions();
|
|
55
|
-
|
|
56
|
+
const request = Object.assign(Object.assign({}, config), options);
|
|
57
|
+
return (0, axios_1.default)(Object.assign(Object.assign(Object.assign({}, (request.proxy === undefined ? proxyFor(config.url) : {})), request), { headers: Object.assign(Object.assign({}, ((_a = config.headers) !== null && _a !== void 0 ? _a : {})), ((_b = options.headers) !== null && _b !== void 0 ? _b : {})) }));
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
60
|
getScreenShot() {
|
|
@@ -392,7 +394,7 @@ class RemoteSession extends XenonSession_1.XenonSession {
|
|
|
392
394
|
logger_1.default.warn(`[RemoteSession] Session ${this.sessionId} probe returned 404. Performing server status fallback...`);
|
|
393
395
|
// Fallback: Verify Appium server is alive
|
|
394
396
|
try {
|
|
395
|
-
const statusRes = yield axios_1.default.get(`${appiumUrl}/status`, { timeout: 3000 });
|
|
397
|
+
const statusRes = yield axios_1.default.get(`${appiumUrl}/status`, Object.assign({ timeout: 3000 }, proxyFor(`${appiumUrl}/status`)));
|
|
396
398
|
if (statusRes.status === 200) {
|
|
397
399
|
// If server is alive but we got a raw 404 with no specific error,
|
|
398
400
|
// it's likely the session is gone in Appium 2 (where /sessions list check is unsupported).
|
|
@@ -438,3 +440,11 @@ class RemoteSession extends XenonSession_1.XenonSession {
|
|
|
438
440
|
}
|
|
439
441
|
}
|
|
440
442
|
exports.RemoteSession = RemoteSession;
|
|
443
|
+
function proxyFor(url) {
|
|
444
|
+
try {
|
|
445
|
+
return url ? (0, outboundProxy_1.axiosProxyConfig)(url) : {};
|
|
446
|
+
}
|
|
447
|
+
catch (_a) {
|
|
448
|
+
return {};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
@@ -62,6 +62,9 @@ const device_store_1 = require("../data-service/device-store");
|
|
|
62
62
|
const nodeWebDriverUrl_1 = require("../gateway/nodeWebDriverUrl");
|
|
63
63
|
const XenonCapabilityManager_1 = require("../XenonCapabilityManager");
|
|
64
64
|
const nodeMetrics_1 = require("../services/metrics/nodeMetrics");
|
|
65
|
+
// A session still running at boot that can't be picked up again is filed
|
|
66
|
+
// under it; the failure analysis also writes it for a shutdown's drain.
|
|
67
|
+
const failureCategories_1 = require("../dashboard/services/failureCategories");
|
|
65
68
|
/**
|
|
66
69
|
* SessionManager with persistence and recovery capabilities.
|
|
67
70
|
*
|
|
@@ -306,7 +309,7 @@ let SessionManager = class SessionManager {
|
|
|
306
309
|
status: 'failed',
|
|
307
310
|
endTime: new Date(),
|
|
308
311
|
failure_reason: reason,
|
|
309
|
-
failure_category:
|
|
312
|
+
failure_category: failureCategories_1.HUB_RESTART_CATEGORY,
|
|
310
313
|
},
|
|
311
314
|
});
|
|
312
315
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.forgetSessionMemory = forgetSessionMemory;
|
|
4
|
+
const typedi_1 = require("typedi");
|
|
5
|
+
const OmniVisionService_1 = require("../services/omni-vision/OmniVisionService");
|
|
6
|
+
const AutowaitService_1 = require("../services/autowait/AutowaitService");
|
|
7
|
+
/**
|
|
8
|
+
* Drop what this server keeps in memory for a session's commands: the
|
|
9
|
+
* elements its finds and heals found in screenshots (OmniVisionService) and
|
|
10
|
+
* its autowait settings (AutowaitService). Called however the session ends
|
|
11
|
+
* here: the test's delete (SessionLifecycleService.deleteSession), Appium's
|
|
12
|
+
* new-command timeout or a crash (XenonPlugin.onUnexpectedShutdown), the idle
|
|
13
|
+
* release (releaseBlockedDevices), a stale heartbeat (OrphanSweeper) and a
|
|
14
|
+
* shutdown's drain (stopSessionForShutdown). Safe to call more than once.
|
|
15
|
+
*
|
|
16
|
+
* Through 2.14 the elements were never dropped, and the autowait settings
|
|
17
|
+
* only by the interceptor on `deleteSession`, a command Appium never hands
|
|
18
|
+
* it: XenonPlugin answers `deleteSession` itself.
|
|
19
|
+
*/
|
|
20
|
+
function forgetSessionMemory(sessionId) {
|
|
21
|
+
if (!sessionId)
|
|
22
|
+
return;
|
|
23
|
+
typedi_1.Container.get(OmniVisionService_1.OmniVisionService).forgetSession(sessionId);
|
|
24
|
+
typedi_1.Container.get(AutowaitService_1.AutowaitService).clearSession(sessionId);
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TestDriverLog = void 0;
|
|
13
|
+
const line_consuming_log_1 = require("appium-xcuitest-driver/build/lib/device/log/line-consuming-log");
|
|
14
|
+
const quietLog = { debug() { }, info() { }, warn() { }, error() { } };
|
|
15
|
+
/**
|
|
16
|
+
* The XCUITest driver's own log class, as an iPhone's (`IOSDeviceLog`) and a
|
|
17
|
+
* simulator's (`IOSSimulatorLog`) `logs.syslog` extend it, with `line()` in
|
|
18
|
+
* place of a device: what Xenon reads is what the driver keeps.
|
|
19
|
+
*/
|
|
20
|
+
class TestDriverLog extends line_consuming_log_1.LineConsumingLog {
|
|
21
|
+
constructor() {
|
|
22
|
+
super({ log: quietLog });
|
|
23
|
+
}
|
|
24
|
+
startCapture() {
|
|
25
|
+
return __awaiter(this, void 0, void 0, function* () { });
|
|
26
|
+
}
|
|
27
|
+
stopCapture() {
|
|
28
|
+
return __awaiter(this, void 0, void 0, function* () { });
|
|
29
|
+
}
|
|
30
|
+
get isCapturing() {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
/** A line from the device, as the driver's capture hands it on. */
|
|
34
|
+
line(message) {
|
|
35
|
+
this.broadcast(message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.TestDriverLog = TestDriverLog;
|