@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
|
@@ -38,6 +38,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
return result;
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
42
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
43
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
44
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
45
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
46
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
47
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
48
|
+
});
|
|
49
|
+
};
|
|
41
50
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
51
|
exports.TracingService = void 0;
|
|
43
52
|
const api_1 = require("@opentelemetry/api");
|
|
@@ -53,15 +62,32 @@ const resources_1 = require("@opentelemetry/resources");
|
|
|
53
62
|
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
54
63
|
const typedi_1 = require("typedi");
|
|
55
64
|
const logger_1 = __importStar(require("../logger"));
|
|
65
|
+
/** Why a session's span ended, on the span. */
|
|
66
|
+
const SESSION_STOP_REASON = 'xenon.session.stop_reason';
|
|
67
|
+
function failSpan(span, error) {
|
|
68
|
+
var _a;
|
|
69
|
+
const exception = error instanceof Error ? error : { message: String((_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error) };
|
|
70
|
+
span.recordException(exception);
|
|
71
|
+
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: exception.message });
|
|
72
|
+
}
|
|
56
73
|
let TracingService = class TracingService {
|
|
57
74
|
constructor() {
|
|
58
75
|
this.sdk = null;
|
|
76
|
+
// Built here only while tracing is off; with it on, the NodeSDK owns them.
|
|
59
77
|
this.loggerProvider = null;
|
|
60
78
|
this.meterProvider = null;
|
|
79
|
+
// Whether Xenon's own log export and metric export are configured.
|
|
80
|
+
this.logsOn = false;
|
|
81
|
+
this.metricsOn = false;
|
|
61
82
|
this.tracer = api_1.trace.getTracer('xenon-core');
|
|
62
83
|
this.activeSpans = new Map();
|
|
84
|
+
// Which of activeSpans are sessions' (keyed by session id) rather than
|
|
85
|
+
// commands' (`<sessionId>:<command>`).
|
|
86
|
+
this.sessionSpanIds = new Set();
|
|
87
|
+
this.spanProcessorsInUse = [];
|
|
63
88
|
}
|
|
64
89
|
initialize(opts = { isHub: true }) {
|
|
90
|
+
var _a;
|
|
65
91
|
if (process.env.OTEL_SDK_DISABLED === 'true') {
|
|
66
92
|
logger_1.default.info('[TracingService] OTEL_SDK_DISABLED=true — OTel disabled.');
|
|
67
93
|
return;
|
|
@@ -71,14 +97,56 @@ let TracingService = class TracingService {
|
|
|
71
97
|
[semantic_conventions_1.ATTR_SERVICE_NAME]: 'xenon',
|
|
72
98
|
service: role,
|
|
73
99
|
});
|
|
74
|
-
this.
|
|
75
|
-
this.
|
|
76
|
-
this.
|
|
100
|
+
const spanProcessors = this.spanProcessors();
|
|
101
|
+
const logRecordProcessors = this.logRecordProcessors();
|
|
102
|
+
const metricReaders = this.metricReaders();
|
|
103
|
+
if (spanProcessors.length > 0) {
|
|
104
|
+
// A NodeSDK that isn't handed log processors and metric readers builds
|
|
105
|
+
// its own from the standard OTEL_* variables (OTLP exporters at
|
|
106
|
+
// OTEL_EXPORTER_OTLP_ENDPOINT + /v1/logs and /v1/metrics, which is
|
|
107
|
+
// Xenon's trace URL) and registers them globally before Xenon could, so
|
|
108
|
+
// Xenon's settings for logs and metrics did nothing while tracing was
|
|
109
|
+
// on. It gets Xenon's, or none. With none for logs it still registers
|
|
110
|
+
// a logger provider, one that exports nothing.
|
|
111
|
+
this.spanProcessorsInUse = spanProcessors;
|
|
112
|
+
this.sdk = new sdk_node_1.NodeSDK({
|
|
113
|
+
resource,
|
|
114
|
+
spanProcessors,
|
|
115
|
+
logRecordProcessors,
|
|
116
|
+
metricReaders,
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
this.sdk.start();
|
|
120
|
+
logger_1.default.info('[TracingService] Trace SDK started.');
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
logger_1.default.error(`[TracingService] Failed to start trace SDK: ${err.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
if (logRecordProcessors.length > 0) {
|
|
128
|
+
this.loggerProvider = new sdk_logs_1.LoggerProvider({ resource, processors: logRecordProcessors });
|
|
129
|
+
api_logs_1.logs.setGlobalLoggerProvider(this.loggerProvider);
|
|
130
|
+
}
|
|
131
|
+
if (metricReaders.length > 0) {
|
|
132
|
+
this.meterProvider = new sdk_metrics_1.MeterProvider({ resource, readers: metricReaders });
|
|
133
|
+
api_1.metrics.setGlobalMeterProvider(this.meterProvider);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (logRecordProcessors.length > 0) {
|
|
137
|
+
this.logsOn = true;
|
|
138
|
+
logger_1.XenonLogger.setOtlpReady(true);
|
|
139
|
+
logger_1.default.info(`[TracingService] Log OTLP endpoint: ${process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}. Log SDK started.`);
|
|
140
|
+
}
|
|
141
|
+
if (metricReaders.length > 0) {
|
|
142
|
+
this.metricsOn = true;
|
|
143
|
+
logger_1.default.info(`[TracingService] Metric OTLP endpoint: ${(_a = process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) !== null && _a !== void 0 ? _a : '(console-only)'}. Metrics SDK started.`);
|
|
144
|
+
}
|
|
77
145
|
}
|
|
78
|
-
|
|
146
|
+
spanProcessors() {
|
|
79
147
|
if (process.env.OTEL_TRACES_ENABLED === 'false') {
|
|
80
148
|
logger_1.default.info('[TracingService] OTEL_TRACES_ENABLED=false — trace export disabled.');
|
|
81
|
-
return;
|
|
149
|
+
return [];
|
|
82
150
|
}
|
|
83
151
|
const exporters = [];
|
|
84
152
|
if (process.env.XENON_OTEL_DEBUG === 'true') {
|
|
@@ -93,52 +161,37 @@ let TracingService = class TracingService {
|
|
|
93
161
|
}
|
|
94
162
|
if (exporters.length === 0) {
|
|
95
163
|
logger_1.default.info('[TracingService] No trace exporters configured. Spans recorded in-memory only.');
|
|
96
|
-
return;
|
|
164
|
+
return [];
|
|
97
165
|
}
|
|
98
166
|
// Pre-existing pre-PR-86 bug: only exporters[0] was wired, so combining
|
|
99
167
|
// XENON_OTEL_DEBUG=true with an OTLP endpoint silently dropped one of the
|
|
100
168
|
// two destinations. Each exporter now gets its own SimpleSpanProcessor.
|
|
101
|
-
|
|
102
|
-
this.sdk = new sdk_node_1.NodeSDK({
|
|
103
|
-
resource,
|
|
104
|
-
spanProcessors,
|
|
105
|
-
});
|
|
106
|
-
try {
|
|
107
|
-
this.sdk.start();
|
|
108
|
-
logger_1.default.info('[TracingService] Trace SDK started.');
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
logger_1.default.error(`[TracingService] Failed to start trace SDK: ${err.message}`);
|
|
112
|
-
}
|
|
169
|
+
return exporters.map((e) => new sdk_trace_base_1.SimpleSpanProcessor(e));
|
|
113
170
|
}
|
|
114
|
-
|
|
171
|
+
logRecordProcessors() {
|
|
115
172
|
if (process.env.OTEL_LOGS_ENABLED === 'false') {
|
|
116
173
|
logger_1.default.info('[TracingService] OTEL_LOGS_ENABLED=false — log export disabled.');
|
|
117
|
-
return;
|
|
174
|
+
return [];
|
|
118
175
|
}
|
|
119
176
|
const endpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
|
|
120
177
|
if (!endpoint)
|
|
121
|
-
return;
|
|
178
|
+
return [];
|
|
122
179
|
const processors = [];
|
|
123
180
|
if (process.env.XENON_OTEL_DEBUG === 'true') {
|
|
124
181
|
processors.push(new sdk_logs_1.SimpleLogRecordProcessor(new sdk_logs_1.ConsoleLogRecordExporter()));
|
|
125
182
|
}
|
|
126
183
|
processors.push(new sdk_logs_1.BatchLogRecordProcessor(new exporter_logs_otlp_http_1.OTLPLogExporter({ url: endpoint })));
|
|
127
|
-
|
|
128
|
-
api_logs_1.logs.setGlobalLoggerProvider(provider);
|
|
129
|
-
this.loggerProvider = provider;
|
|
130
|
-
logger_1.XenonLogger.setOtlpReady(true);
|
|
131
|
-
logger_1.default.info(`[TracingService] Log OTLP endpoint: ${endpoint}. Log SDK started.`);
|
|
184
|
+
return processors;
|
|
132
185
|
}
|
|
133
|
-
|
|
186
|
+
metricReaders() {
|
|
134
187
|
if (process.env.OTEL_METRICS_ENABLED === 'false') {
|
|
135
188
|
logger_1.default.info('[TracingService] OTEL_METRICS_ENABLED=false — metrics export disabled.');
|
|
136
|
-
return;
|
|
189
|
+
return [];
|
|
137
190
|
}
|
|
138
191
|
const endpoint = process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT;
|
|
139
192
|
const debug = process.env.XENON_OTEL_DEBUG === 'true';
|
|
140
193
|
if (!endpoint && !debug)
|
|
141
|
-
return;
|
|
194
|
+
return [];
|
|
142
195
|
const readers = [];
|
|
143
196
|
if (debug) {
|
|
144
197
|
// 10s interval keeps console output readable during dev sessions.
|
|
@@ -154,18 +207,15 @@ let TracingService = class TracingService {
|
|
|
154
207
|
exportIntervalMillis: 60000,
|
|
155
208
|
}));
|
|
156
209
|
}
|
|
157
|
-
|
|
158
|
-
api_1.metrics.setGlobalMeterProvider(provider);
|
|
159
|
-
this.meterProvider = provider;
|
|
160
|
-
logger_1.default.info(`[TracingService] Metric OTLP endpoint: ${endpoint !== null && endpoint !== void 0 ? endpoint : '(console-only)'}. Metrics SDK started.`);
|
|
210
|
+
return readers;
|
|
161
211
|
}
|
|
162
212
|
getLogger(name = 'xenon-core') {
|
|
163
|
-
if (!this.
|
|
213
|
+
if (!this.logsOn)
|
|
164
214
|
return undefined;
|
|
165
215
|
return api_logs_1.logs.getLogger(name);
|
|
166
216
|
}
|
|
167
217
|
getMeter(name = 'xenon-core') {
|
|
168
|
-
if (!this.
|
|
218
|
+
if (!this.metricsOn)
|
|
169
219
|
return undefined;
|
|
170
220
|
return api_1.metrics.getMeter(name);
|
|
171
221
|
}
|
|
@@ -177,8 +227,18 @@ let TracingService = class TracingService {
|
|
|
177
227
|
attributes: Object.assign({ 'xenon.session_id': sessionId }, attributes),
|
|
178
228
|
});
|
|
179
229
|
this.activeSpans.set(sessionId, span);
|
|
230
|
+
this.sessionSpanIds.add(sessionId);
|
|
180
231
|
return span;
|
|
181
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Ends a session's span. Every way a session ends calls this (the client's
|
|
235
|
+
* delete, Appium's new-command timeout, the idle release, the heartbeat
|
|
236
|
+
* sweeps, a shutdown's drain), so whichever comes second finds nothing.
|
|
237
|
+
* `failed` for a session that ended failed; `reason` says why it ended.
|
|
238
|
+
*/
|
|
239
|
+
endSessionSpan(sessionId, ending = {}) {
|
|
240
|
+
this.endSpan(sessionId, ending.failed ? 'ERROR' : 'OK', ending.reason ? { [SESSION_STOP_REASON]: ending.reason } : {});
|
|
241
|
+
}
|
|
182
242
|
startCommandSpan(sessionId, commandName, attributes = {}) {
|
|
183
243
|
if (!this.sdk)
|
|
184
244
|
return undefined;
|
|
@@ -202,28 +262,34 @@ let TracingService = class TracingService {
|
|
|
202
262
|
if (!this.sdk)
|
|
203
263
|
return;
|
|
204
264
|
const span = this.activeSpans.get(id);
|
|
205
|
-
if (span)
|
|
206
|
-
if (Object.keys(attributes).length > 0) {
|
|
207
|
-
span.setAttributes(attributes);
|
|
208
|
-
}
|
|
209
|
-
span.setStatus({
|
|
210
|
-
code: status === 'OK' ? api_1.SpanStatusCode.OK : api_1.SpanStatusCode.ERROR,
|
|
211
|
-
});
|
|
212
|
-
span.end();
|
|
213
|
-
this.activeSpans.delete(id);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
recordError(id, error) {
|
|
217
|
-
if (!this.sdk)
|
|
265
|
+
if (!span)
|
|
218
266
|
return;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
span.
|
|
223
|
-
code: api_1.SpanStatusCode.ERROR,
|
|
224
|
-
message: typeof error === 'string' ? error : error.message,
|
|
225
|
-
});
|
|
267
|
+
this.activeSpans.delete(id);
|
|
268
|
+
this.sessionSpanIds.delete(id);
|
|
269
|
+
if (Object.keys(attributes).length > 0) {
|
|
270
|
+
span.setAttributes(attributes);
|
|
226
271
|
}
|
|
272
|
+
span.setStatus({
|
|
273
|
+
code: status === 'OK' ? api_1.SpanStatusCode.OK : api_1.SpanStatusCode.ERROR,
|
|
274
|
+
});
|
|
275
|
+
span.end();
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Ends the span a command started: ERROR with the exception when it
|
|
279
|
+
* `failed`, else OK. By the span itself, not by its id: two commands of the
|
|
280
|
+
* same name on one session at once (the Inspector reading the page source
|
|
281
|
+
* while a test does) share `<sessionId>:<command>`, and the second's span
|
|
282
|
+
* replaced the first's under it, so one command's error went on the other's
|
|
283
|
+
* span and its own never ended.
|
|
284
|
+
*/
|
|
285
|
+
endCommandSpan(id, span, failed) {
|
|
286
|
+
if (failed)
|
|
287
|
+
failSpan(span, failed.error);
|
|
288
|
+
else
|
|
289
|
+
span.setStatus({ code: api_1.SpanStatusCode.OK });
|
|
290
|
+
span.end();
|
|
291
|
+
if (this.activeSpans.get(id) === span)
|
|
292
|
+
this.activeSpans.delete(id);
|
|
227
293
|
}
|
|
228
294
|
getTraceId(sessionId) {
|
|
229
295
|
var _a;
|
|
@@ -233,12 +299,57 @@ let TracingService = class TracingService {
|
|
|
233
299
|
var _a;
|
|
234
300
|
return (_a = this.activeSpans.get(id)) === null || _a === void 0 ? void 0 : _a.spanContext().spanId;
|
|
235
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* shutdown(), waiting no longer than `budgetMs`: an export to a collector
|
|
304
|
+
* that doesn't answer is held for the exporter's own timeout (10 s), and a
|
|
305
|
+
* process that is exiting can't wait that long.
|
|
306
|
+
*/
|
|
307
|
+
shutdownWithin(budgetMs) {
|
|
308
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
309
|
+
let timer;
|
|
310
|
+
try {
|
|
311
|
+
yield Promise.race([
|
|
312
|
+
this.shutdown(),
|
|
313
|
+
new Promise((resolve) => {
|
|
314
|
+
timer = setTimeout(resolve, budgetMs);
|
|
315
|
+
}),
|
|
316
|
+
]);
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
if (timer)
|
|
320
|
+
clearTimeout(timer);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Stops every exporter, once what each still holds is sent. A session span
|
|
326
|
+
* still open belongs to a session this process didn't end: one running on a
|
|
327
|
+
* node or at a cloud provider, which a hub's shutdown leaves running, or one
|
|
328
|
+
* whose ending was missed. It ends here, saying so, or it is never exported.
|
|
329
|
+
*/
|
|
236
330
|
shutdown() {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
331
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
332
|
+
for (const sessionId of [...this.sessionSpanIds]) {
|
|
333
|
+
const span = this.activeSpans.get(sessionId);
|
|
334
|
+
this.activeSpans.delete(sessionId);
|
|
335
|
+
span === null || span === void 0 ? void 0 : span.setAttribute(SESSION_STOP_REASON, 'Xenon shut down while the session was running');
|
|
336
|
+
span === null || span === void 0 ? void 0 : span.end();
|
|
337
|
+
}
|
|
338
|
+
this.sessionSpanIds.clear();
|
|
339
|
+
// A span processor's shutdown doesn't wait for the exports it started,
|
|
340
|
+
// and with the NodeSDK's resource detection every export waits on it.
|
|
341
|
+
yield Promise.allSettled(this.spanProcessorsInUse.map((p) => p.forceFlush()));
|
|
342
|
+
this.spanProcessorsInUse = [];
|
|
343
|
+
logger_1.XenonLogger.setOtlpReady(false);
|
|
344
|
+
const stopping = [this.sdk, this.loggerProvider, this.meterProvider].map((owner) => owner === null || owner === void 0 ? void 0 : owner.shutdown());
|
|
345
|
+
this.sdk = null;
|
|
346
|
+
this.loggerProvider = null;
|
|
347
|
+
this.meterProvider = null;
|
|
348
|
+
this.logsOn = false;
|
|
349
|
+
this.metricsOn = false;
|
|
350
|
+
this.activeSpans.clear();
|
|
351
|
+
yield Promise.allSettled(stopping);
|
|
352
|
+
});
|
|
242
353
|
}
|
|
243
354
|
};
|
|
244
355
|
exports.TracingService = TracingService;
|
|
@@ -24,6 +24,7 @@ const bcrypt_1 = __importDefault(require("bcrypt"));
|
|
|
24
24
|
const crypto_1 = __importDefault(require("crypto"));
|
|
25
25
|
const logger_1 = __importDefault(require("../logger"));
|
|
26
26
|
const prisma_1 = require("../prisma");
|
|
27
|
+
const identityChanges_1 = require("./identity/identityChanges");
|
|
27
28
|
const ACCESS_KEY_PREFIX = 'xen_';
|
|
28
29
|
const ACCESS_KEY_LEN = 12;
|
|
29
30
|
const PASSWORD_MIN = 8;
|
|
@@ -92,14 +93,18 @@ let UserService = class UserService {
|
|
|
92
93
|
return prisma_1.prisma.user.findUnique({ where: { id } });
|
|
93
94
|
});
|
|
94
95
|
}
|
|
96
|
+
// The old access key's (accessKey, token) pairs stop working, a node's
|
|
97
|
+
// socket's included.
|
|
95
98
|
rotateAccessKey(userId) {
|
|
96
99
|
return __awaiter(this, void 0, void 0, function* () {
|
|
97
100
|
const accessKey = this.generateAccessKey();
|
|
98
|
-
|
|
101
|
+
const updated = yield prisma_1.prisma.user.update({
|
|
99
102
|
where: { id: userId },
|
|
100
103
|
data: { accessKey },
|
|
101
104
|
select: { id: true, accessKey: true },
|
|
102
105
|
});
|
|
106
|
+
yield (0, identityChanges_1.identityChanged)(userId);
|
|
107
|
+
return updated;
|
|
103
108
|
});
|
|
104
109
|
}
|
|
105
110
|
changePassword(userId, oldPassword, newPassword) {
|
|
@@ -163,18 +168,23 @@ let UserService = class UserService {
|
|
|
163
168
|
data.role = patch.role;
|
|
164
169
|
if (patch.status !== undefined)
|
|
165
170
|
data.status = patch.status;
|
|
166
|
-
|
|
171
|
+
const updated = yield prisma_1.prisma.user.update({ where: { id: userId }, data });
|
|
172
|
+
yield (0, identityChanges_1.identityChanged)(userId);
|
|
173
|
+
return updated;
|
|
167
174
|
});
|
|
168
175
|
}
|
|
169
176
|
setRole(userId, role) {
|
|
170
177
|
return __awaiter(this, void 0, void 0, function* () {
|
|
171
|
-
|
|
178
|
+
const updated = yield prisma_1.prisma.user.update({ where: { id: userId }, data: { role } });
|
|
179
|
+
yield (0, identityChanges_1.identityChanged)(userId);
|
|
180
|
+
return updated;
|
|
172
181
|
});
|
|
173
182
|
}
|
|
174
183
|
deactivateUser(userId) {
|
|
175
184
|
return __awaiter(this, void 0, void 0, function* () {
|
|
176
185
|
yield prisma_1.prisma.user.update({ where: { id: userId }, data: { status: 'INACTIVE' } });
|
|
177
186
|
yield prisma_1.prisma.userSession.deleteMany({ where: { userId } });
|
|
187
|
+
yield (0, identityChanges_1.identityChanged)(userId);
|
|
178
188
|
});
|
|
179
189
|
}
|
|
180
190
|
deleteUser(userId) {
|
|
@@ -184,6 +194,7 @@ let UserService = class UserService {
|
|
|
184
194
|
// relying on cascade semantics for security-critical cleanup.
|
|
185
195
|
yield prisma_1.prisma.userSession.deleteMany({ where: { userId } });
|
|
186
196
|
yield prisma_1.prisma.user.delete({ where: { id: userId } });
|
|
197
|
+
yield (0, identityChanges_1.identityChanged)(userId);
|
|
187
198
|
});
|
|
188
199
|
}
|
|
189
200
|
};
|
|
@@ -22,6 +22,7 @@ exports.UserSessionService = void 0;
|
|
|
22
22
|
const typedi_1 = require("typedi");
|
|
23
23
|
const prisma_1 = require("../prisma");
|
|
24
24
|
const logger_1 = __importDefault(require("../logger"));
|
|
25
|
+
const identityChanges_1 = require("./identity/identityChanges");
|
|
25
26
|
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
26
27
|
let UserSessionService = class UserSessionService {
|
|
27
28
|
constructor() {
|
|
@@ -38,13 +39,20 @@ let UserSessionService = class UserSessionService {
|
|
|
38
39
|
});
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
/**
|
|
43
|
+
* The sign-in, if it exists and hasn't expired. A use of it renews it
|
|
44
|
+
* (sliding TTL); `renew: false` only looks, for a check that isn't a use: a
|
|
45
|
+
* socket's re-check must not keep a sign-in alive for as long as a tab is open.
|
|
46
|
+
*/
|
|
47
|
+
resolve(sessionId_1) {
|
|
48
|
+
return __awaiter(this, arguments, void 0, function* (sessionId, { renew = true } = {}) {
|
|
43
49
|
const row = yield prisma_1.prisma.userSession.findUnique({ where: { id: sessionId } });
|
|
44
50
|
if (!row)
|
|
45
51
|
return null;
|
|
46
52
|
if (row.expiresAt.getTime() < Date.now())
|
|
47
53
|
return null;
|
|
54
|
+
if (!renew)
|
|
55
|
+
return row;
|
|
48
56
|
const newExpiresAt = new Date(Date.now() + this.ttlMs());
|
|
49
57
|
// Fire-and-forget sliding-TTL update; failures must not block auth, but
|
|
50
58
|
// log them at debug level so chronic write issues are diagnosable.
|
|
@@ -62,13 +70,15 @@ let UserSessionService = class UserSessionService {
|
|
|
62
70
|
// which is fine. Other errors get logged for diagnosis but don't bubble.
|
|
63
71
|
revoke(sessionId) {
|
|
64
72
|
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
-
yield prisma_1.prisma.userSession.delete({ where: { id: sessionId } }).catch((e) => {
|
|
73
|
+
const row = yield prisma_1.prisma.userSession.delete({ where: { id: sessionId } }).catch((e) => {
|
|
66
74
|
var _a;
|
|
67
75
|
const code = e === null || e === void 0 ? void 0 : e.code;
|
|
68
76
|
if (code === 'P2025')
|
|
69
|
-
return;
|
|
77
|
+
return null;
|
|
70
78
|
this.log.warn(`revoke: unexpected delete error for session ${sessionId}: ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`);
|
|
79
|
+
return null;
|
|
71
80
|
});
|
|
81
|
+
yield (0, identityChanges_1.identityChanged)(row === null || row === void 0 ? void 0 : row.userId);
|
|
72
82
|
});
|
|
73
83
|
}
|
|
74
84
|
revokeAllForUserExcept(userId, keepSessionId) {
|
|
@@ -78,6 +88,7 @@ let UserSessionService = class UserSessionService {
|
|
|
78
88
|
});
|
|
79
89
|
if (r.count > 0) {
|
|
80
90
|
this.log.info(`revoked ${r.count} sessions for user ${userId} (kept ${keepSessionId})`);
|
|
91
|
+
yield (0, identityChanges_1.identityChanged)(userId);
|
|
81
92
|
}
|
|
82
93
|
return r.count;
|
|
83
94
|
});
|
|
@@ -87,6 +98,7 @@ let UserSessionService = class UserSessionService {
|
|
|
87
98
|
const r = yield prisma_1.prisma.userSession.deleteMany({ where: { userId } });
|
|
88
99
|
if (r.count > 0) {
|
|
89
100
|
this.log.info(`revoked all ${r.count} sessions for user ${userId}`);
|
|
101
|
+
yield (0, identityChanges_1.identityChanged)(userId);
|
|
90
102
|
}
|
|
91
103
|
return r.count;
|
|
92
104
|
});
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.sliceVideo = sliceVideo;
|
|
7
7
|
const child_process_1 = require("child_process");
|
|
8
|
+
const ffmpegPath_1 = require("../../helpers/ffmpegPath");
|
|
8
9
|
const logger_1 = __importDefault(require("../../logger"));
|
|
9
10
|
function sliceVideo(inputPath, startSec, endSec, outPath, spawnFn = child_process_1.spawn) {
|
|
10
11
|
return new Promise((resolve) => {
|
|
@@ -16,7 +17,9 @@ function sliceVideo(inputPath, startSec, endSec, outPath, spawnFn = child_proces
|
|
|
16
17
|
'-c', 'copy',
|
|
17
18
|
outPath,
|
|
18
19
|
];
|
|
19
|
-
|
|
20
|
+
// Xenon's own ffmpeg, as everywhere else: a bare name isn't found by a
|
|
21
|
+
// server the Mac app started, and finds a system ffmpeg first otherwise.
|
|
22
|
+
const proc = spawnFn((0, ffmpegPath_1.resolveFfmpegPath)(), args);
|
|
20
23
|
let stderr = '';
|
|
21
24
|
if (proc.stderr) {
|
|
22
25
|
proc.stderr.on('data', (chunk) => {
|
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.canOverrideLease = canOverrideLease;
|
|
4
4
|
function canOverrideLease(credential) {
|
|
5
|
-
var _a, _b;
|
|
6
5
|
switch (credential.kind) {
|
|
7
6
|
case 'auth-disabled':
|
|
8
7
|
return true;
|
|
9
|
-
case 'api-key':
|
|
8
|
+
case 'api-key':
|
|
9
|
+
case 'session-token': {
|
|
10
10
|
if (!credential.user)
|
|
11
11
|
return false;
|
|
12
12
|
const scopes = new Set(credential.scopes.split(',').map((s) => s.trim()));
|
|
13
|
-
|
|
13
|
+
const { role } = credential.user;
|
|
14
|
+
if (credential.kind === 'session-token') {
|
|
15
|
+
return role === 'SUPER_ADMIN' || (role === 'ADMIN' && scopes.has('admin'));
|
|
16
|
+
}
|
|
17
|
+
return scopes.has('admin') || role === 'SUPER_ADMIN';
|
|
14
18
|
}
|
|
15
|
-
case 'session-token':
|
|
16
|
-
return ((_a = credential.user) === null || _a === void 0 ? void 0 : _a.role) === 'SUPER_ADMIN' || ((_b = credential.user) === null || _b === void 0 ? void 0 : _b.role) === 'ADMIN';
|
|
17
19
|
default:
|
|
18
20
|
return false;
|
|
19
21
|
}
|
|
@@ -20,6 +20,9 @@ const xpath_1 = require("xpath");
|
|
|
20
20
|
const typedi_1 = require("typedi");
|
|
21
21
|
const logger_1 = __importDefault(require("../../logger"));
|
|
22
22
|
const HealedLocatorGenerator_1 = require("./HealedLocatorGenerator");
|
|
23
|
+
const fingerprintIdentity_1 = require("./fingerprintIdentity");
|
|
24
|
+
/** What an element says in words, for telling it apart once its identifier differs. */
|
|
25
|
+
const TEXTS = ['text', 'label', 'content-desc'];
|
|
23
26
|
class FuzzyXmlHealingProvider {
|
|
24
27
|
constructor(etalonService) {
|
|
25
28
|
this.etalonService = etalonService;
|
|
@@ -41,6 +44,12 @@ class FuzzyXmlHealingProvider {
|
|
|
41
44
|
if (this.etalonService) {
|
|
42
45
|
etalon = yield this.etalonService.getSignature(context.selector);
|
|
43
46
|
}
|
|
47
|
+
// A fingerprint with only a rect and a type (every learnt one through
|
|
48
|
+
// 2.14) would heal to whatever element of that type is at that spot.
|
|
49
|
+
if (etalon && !(0, fingerprintIdentity_1.hasIdentity)(etalon.attributes)) {
|
|
50
|
+
this.logger.info('The baseline signature says nothing about which element it is; matching without it.');
|
|
51
|
+
etalon = null;
|
|
52
|
+
}
|
|
44
53
|
const keywords = this.extractKeywords(context.selector);
|
|
45
54
|
if (keywords.length === 0 && !etalon)
|
|
46
55
|
return null;
|
|
@@ -135,6 +144,9 @@ class FuzzyXmlHealingProvider {
|
|
|
135
144
|
if (etalonTag !== nodeTag && etalonTag !== 'xcuielementtypeany' && etalonTag !== 'unknown') {
|
|
136
145
|
return 0; // Immediate rejection — wrong element type
|
|
137
146
|
}
|
|
147
|
+
// Position can't heal to an element whose identifier says it's another one.
|
|
148
|
+
if (this.contradictsIdentifier(node, etalon))
|
|
149
|
+
return 0;
|
|
138
150
|
// Tag matches perfectly
|
|
139
151
|
totalWeight += 1.0;
|
|
140
152
|
totalScore += 1.0;
|
|
@@ -260,6 +272,45 @@ class FuzzyXmlHealingProvider {
|
|
|
260
272
|
}
|
|
261
273
|
return finalScore;
|
|
262
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Whether `node` is, by its identifier, another element than the
|
|
277
|
+
* fingerprint's: the fingerprint has an identifier (an Android resource-id,
|
|
278
|
+
* compared without its package, or an iOS accessibility identifier), `node`
|
|
279
|
+
* has another one (sameIdentifier), and no text of it (text, label,
|
|
280
|
+
* content-desc) reads the same as the fingerprint's (sameText). Its
|
|
281
|
+
* position then counts for nothing.
|
|
282
|
+
*
|
|
283
|
+
* A renamed id that kept its text or description still heals, and so does
|
|
284
|
+
* a renamed text that kept its id. An element without an identifier, where
|
|
285
|
+
* the fingerprint has one, is judged by its text alone: an Android element
|
|
286
|
+
* with no resource-id, or an iOS one, whose name is then its label. Only
|
|
287
|
+
* where neither has one does position decide, as through 2.14.
|
|
288
|
+
* What this stops is the button that took the old one's place: "Cancel
|
|
289
|
+
* order" (cancel) where "Pay now" (pay) was, "Log out" where "Log in" was.
|
|
290
|
+
*/
|
|
291
|
+
contradictsIdentifier(node, etalon) {
|
|
292
|
+
const identifier = (0, fingerprintIdentity_1.stableIdentifierOf)(etalon.attributes);
|
|
293
|
+
if (!identifier)
|
|
294
|
+
return false;
|
|
295
|
+
// Another identifier, or none where the fingerprint has one, and the
|
|
296
|
+
// text decides. iOS gives an element without one its label as name.
|
|
297
|
+
const theirs = this.getAttrValue(node, identifier.attribute);
|
|
298
|
+
if ((0, fingerprintIdentity_1.meaningful)(theirs)) {
|
|
299
|
+
const [mine, other] = identifier.attribute === 'resource-id'
|
|
300
|
+
? [(0, fingerprintIdentity_1.resourceIdName)(identifier.value), (0, fingerprintIdentity_1.resourceIdName)(theirs)]
|
|
301
|
+
: [identifier.value, theirs];
|
|
302
|
+
if ((0, fingerprintIdentity_1.sameIdentifier)(mine, other))
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
const texts = TEXTS.map((name) => etalon.attributes[name]).filter(fingerprintIdentity_1.meaningful);
|
|
306
|
+
if (texts.length === 0)
|
|
307
|
+
return true;
|
|
308
|
+
const nodeTexts = [
|
|
309
|
+
node.textContent,
|
|
310
|
+
...TEXTS.map((name) => this.getAttrValue(node, name)),
|
|
311
|
+
].filter(fingerprintIdentity_1.meaningful);
|
|
312
|
+
return !nodeTexts.some((t) => texts.some((w) => (0, fingerprintIdentity_1.sameText)(t, w)));
|
|
313
|
+
}
|
|
263
314
|
getAttrValue(node, name) {
|
|
264
315
|
if (typeof node.getAttribute === 'function') {
|
|
265
316
|
const val = node.getAttribute(name);
|
|
@@ -37,7 +37,9 @@ let HealEtalonService = class HealEtalonService {
|
|
|
37
37
|
return __awaiter(this, void 0, void 0, function* () {
|
|
38
38
|
try {
|
|
39
39
|
const attributes = {};
|
|
40
|
-
// Extract identifying attributes
|
|
40
|
+
// Extract identifying attributes. Not `value`: on a text field it is
|
|
41
|
+
// what the test typed, an email or a name, which says nothing about
|
|
42
|
+
// which field it is.
|
|
41
43
|
const nodeAttrs = node.attributes || [];
|
|
42
44
|
const anchorNames = [
|
|
43
45
|
'content-desc',
|
|
@@ -48,7 +50,6 @@ let HealEtalonService = class HealEtalonService {
|
|
|
48
50
|
'hint',
|
|
49
51
|
'label',
|
|
50
52
|
'accessibility-id',
|
|
51
|
-
'value',
|
|
52
53
|
'x',
|
|
53
54
|
'y',
|
|
54
55
|
'width',
|