@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
|
@@ -1,43 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
2
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
3
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
4
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
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;
|
|
22
6
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
7
|
};
|
|
24
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
-
var ownKeys = function(o) {
|
|
26
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
-
var ar = [];
|
|
28
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
-
return ar;
|
|
30
|
-
};
|
|
31
|
-
return ownKeys(o);
|
|
32
|
-
};
|
|
33
|
-
return function (mod) {
|
|
34
|
-
if (mod && mod.__esModule) return mod;
|
|
35
|
-
var result = {};
|
|
36
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
-
__setModuleDefault(result, mod);
|
|
38
|
-
return result;
|
|
39
|
-
};
|
|
40
|
-
})();
|
|
41
8
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
42
9
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
43
10
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -52,22 +19,36 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
52
19
|
};
|
|
53
20
|
var OmniVisionService_1;
|
|
54
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
-
exports.OmniVisionService = void 0;
|
|
22
|
+
exports.OmniVisionService = exports.MAX_VIRTUAL_ELEMENTS = exports.MAX_VIRTUAL_ELEMENTS_PER_SESSION = void 0;
|
|
56
23
|
const typedi_1 = require("typedi");
|
|
57
|
-
const tesseract_js_1 =
|
|
24
|
+
const tesseract_js_1 = require("tesseract.js");
|
|
25
|
+
const ocrData_1 = require("../ocr/ocrData");
|
|
58
26
|
const sharp_1 = __importDefault(require("sharp"));
|
|
59
27
|
const AIService_1 = require("../AIService");
|
|
60
28
|
const logger_1 = __importDefault(require("../../logger"));
|
|
61
29
|
const crypto_1 = __importDefault(require("crypto"));
|
|
62
30
|
const ocrTextMatch_1 = require("./ocrTextMatch");
|
|
63
31
|
const screenScale_1 = require("./screenScale");
|
|
32
|
+
/**
|
|
33
|
+
* How many virtual elements Xenon keeps, for one session and in all. The
|
|
34
|
+
* oldest go first: a test acts on what it found last, and a command on an
|
|
35
|
+
* element that went answers "no such element".
|
|
36
|
+
*/
|
|
37
|
+
exports.MAX_VIRTUAL_ELEMENTS_PER_SESSION = 1000;
|
|
38
|
+
exports.MAX_VIRTUAL_ELEMENTS = 10000;
|
|
64
39
|
// Mobile UIs have sparse text on complex backgrounds - reduce contrast to preserve readability
|
|
65
40
|
const DEFAULT_CONTRAST = 1.2; // Slightly above 1.0 for mild enhancement (was 0.8 which was too aggressive)
|
|
66
41
|
const UPSCALE_THRESHOLD = 800; // Only upscale very small images (was 1500)
|
|
67
42
|
let OmniVisionService = OmniVisionService_1 = class OmniVisionService {
|
|
68
43
|
constructor() {
|
|
69
44
|
this.logger = logger_1.default.scope('OmniVision');
|
|
70
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The virtual elements tests may still act on, by id, each with the session
|
|
47
|
+
* that found it, oldest first. Through 2.14 every element ever found stayed
|
|
48
|
+
* here until the server restarted, and any session could act on another's.
|
|
49
|
+
*/
|
|
50
|
+
this.elements = new Map();
|
|
51
|
+
this.bySession = new Map();
|
|
71
52
|
this.sharedWorker = null;
|
|
72
53
|
this.workerBusy = false;
|
|
73
54
|
this.uiLensCache = new Map();
|
|
@@ -77,7 +58,7 @@ let OmniVisionService = OmniVisionService_1 = class OmniVisionService {
|
|
|
77
58
|
if (this.sharedWorker)
|
|
78
59
|
return this.sharedWorker;
|
|
79
60
|
this.logger.info('Initializing persistent OCR worker with explicit configuration...');
|
|
80
|
-
const worker = yield
|
|
61
|
+
const worker = yield (0, ocrData_1.createOcrWorker)();
|
|
81
62
|
// PSM.SPARSE_TEXT is optimized for irregular text layouts like mobile UIs
|
|
82
63
|
// AUTO mode assumes document structure which fails on sparse mobile screens
|
|
83
64
|
yield worker.setParameters({
|
|
@@ -385,6 +366,21 @@ let OmniVisionService = OmniVisionService_1 = class OmniVisionService {
|
|
|
385
366
|
wordBoxes(words) {
|
|
386
367
|
return words.map((w) => this.normalizeWordBBox(w)).filter(Boolean);
|
|
387
368
|
}
|
|
369
|
+
/** Where `text` is among OCR's words, read with over 60% confidence, in reading order. */
|
|
370
|
+
textMatches(words, text) {
|
|
371
|
+
return (0, ocrTextMatch_1.findText)(this.wordBoxes(words), String(text !== null && text !== void 0 ? text : '')).filter((m) => m.confidence > 60);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Where `text` is on a screenshot, matched as `-custom:ai-text` matches it:
|
|
375
|
+
* in the screenshot's pixels, in reading order. Self-healing's OCR tier
|
|
376
|
+
* uses it. Throws when OCR fails.
|
|
377
|
+
*/
|
|
378
|
+
findTextInScreenshot(screenshotBase64, text) {
|
|
379
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
380
|
+
const { words } = yield this.performOcr(Buffer.from(screenshotBase64, 'base64'));
|
|
381
|
+
return this.textMatches(words, text);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
388
384
|
/**
|
|
389
385
|
* Proactive OCR Search: Finds elements matching text even if not in XML.
|
|
390
386
|
*
|
|
@@ -407,20 +403,16 @@ let OmniVisionService = OmniVisionService_1 = class OmniVisionService {
|
|
|
407
403
|
this.logger.warn(`OCR proactive search yielded no words. Text content: "${ocrText.trim().substring(0, 100)}..."`);
|
|
408
404
|
return [];
|
|
409
405
|
}
|
|
410
|
-
const matches =
|
|
406
|
+
const matches = this.textMatches(words, text);
|
|
411
407
|
if (matches.length === 0)
|
|
412
408
|
return [];
|
|
413
409
|
const scale = yield (0, screenScale_1.screenScaleOf)(driver, screenshot);
|
|
414
|
-
return matches.map((m) => {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
};
|
|
421
|
-
this.virtualElementStore.set(el.id, el);
|
|
422
|
-
return el;
|
|
423
|
-
});
|
|
410
|
+
return matches.map((m) => ({
|
|
411
|
+
id: `omni_ocr_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`,
|
|
412
|
+
text: m.text,
|
|
413
|
+
rect: (0, screenScale_1.toDriverRect)({ x: m.x0, y: m.y0, width: m.x1 - m.x0, height: m.y1 - m.y0 }, scale),
|
|
414
|
+
confidence: m.confidence / 100,
|
|
415
|
+
}));
|
|
424
416
|
}
|
|
425
417
|
catch (err) {
|
|
426
418
|
this.logger.error(`OCR proactive search failed: ${err.message}`);
|
|
@@ -443,13 +435,11 @@ let OmniVisionService = OmniVisionService_1 = class OmniVisionService {
|
|
|
443
435
|
// The AI answers in the screenshot's pixels; the rect is the driver's
|
|
444
436
|
// (points on iOS), like a real element's.
|
|
445
437
|
const scale = yield (0, screenScale_1.screenScaleOf)(driver, screenshot);
|
|
446
|
-
|
|
447
|
-
id: `omni_ai_${Date.now()}`,
|
|
438
|
+
return {
|
|
439
|
+
id: `omni_ai_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,
|
|
448
440
|
rect: (0, screenScale_1.toDriverRect)({ x: coordinates.x - 20, y: coordinates.y - 20, width: 40, height: 40 }, scale),
|
|
449
441
|
confidence: 0.85,
|
|
450
442
|
};
|
|
451
|
-
this.virtualElementStore.set(el.id, el);
|
|
452
|
-
return el;
|
|
453
443
|
}
|
|
454
444
|
}
|
|
455
445
|
catch (err) {
|
|
@@ -505,11 +495,48 @@ let OmniVisionService = OmniVisionService_1 = class OmniVisionService {
|
|
|
505
495
|
}
|
|
506
496
|
});
|
|
507
497
|
}
|
|
508
|
-
|
|
509
|
-
|
|
498
|
+
/**
|
|
499
|
+
* Keep `element` for `sessionId`, whose test was handed its id (a
|
|
500
|
+
* `-custom:ai-*` find, a heal), until the session ends (`forgetSession`)
|
|
501
|
+
* or the limits push it out. A find doesn't keep what it finds: device
|
|
502
|
+
* control's locator test only shows the ids, and a tap uses its element at
|
|
503
|
+
* once.
|
|
504
|
+
*/
|
|
505
|
+
remember(sessionId, element) {
|
|
506
|
+
this.drop(element.id);
|
|
507
|
+
this.elements.set(element.id, { element, sessionId });
|
|
508
|
+
let ids = this.bySession.get(sessionId);
|
|
509
|
+
if (!ids) {
|
|
510
|
+
ids = new Set();
|
|
511
|
+
this.bySession.set(sessionId, ids);
|
|
512
|
+
}
|
|
513
|
+
ids.add(element.id);
|
|
514
|
+
if (ids.size > exports.MAX_VIRTUAL_ELEMENTS_PER_SESSION)
|
|
515
|
+
this.drop(ids.values().next().value);
|
|
516
|
+
if (this.elements.size > exports.MAX_VIRTUAL_ELEMENTS)
|
|
517
|
+
this.drop(this.elements.keys().next().value);
|
|
518
|
+
}
|
|
519
|
+
/** The element, when `sessionId` found it and still has it. */
|
|
520
|
+
getVirtualElement(id, sessionId) {
|
|
521
|
+
const held = this.elements.get(id);
|
|
522
|
+
return held && held.sessionId === sessionId ? held.element : undefined;
|
|
523
|
+
}
|
|
524
|
+
/** Drop every element a session found: it has ended (sessionMemory.ts). */
|
|
525
|
+
forgetSession(sessionId) {
|
|
526
|
+
var _a;
|
|
527
|
+
for (const id of (_a = this.bySession.get(sessionId)) !== null && _a !== void 0 ? _a : [])
|
|
528
|
+
this.elements.delete(id);
|
|
529
|
+
this.bySession.delete(sessionId);
|
|
510
530
|
}
|
|
511
|
-
|
|
512
|
-
|
|
531
|
+
drop(id) {
|
|
532
|
+
const held = this.elements.get(id);
|
|
533
|
+
if (!held)
|
|
534
|
+
return;
|
|
535
|
+
this.elements.delete(id);
|
|
536
|
+
const ids = this.bySession.get(held.sessionId);
|
|
537
|
+
ids === null || ids === void 0 ? void 0 : ids.delete(id);
|
|
538
|
+
if (ids && ids.size === 0)
|
|
539
|
+
this.bySession.delete(held.sessionId);
|
|
513
540
|
}
|
|
514
541
|
/**
|
|
515
542
|
* Omni-Click: Find a target by OCR text and click its center using W3C actions.
|
|
@@ -1,25 +1,66 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resetLinkBase = resetLinkBase;
|
|
3
4
|
exports.buildResetLink = buildResetLink;
|
|
5
|
+
exports.requestBase = requestBase;
|
|
6
|
+
exports.describeLifetime = describeLifetime;
|
|
4
7
|
exports.resetEmail = resetEmail;
|
|
8
|
+
const config_1 = require("../config");
|
|
9
|
+
const publicUrl_1 = require("./publicUrl");
|
|
5
10
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* until it expires or is used: never log the result.
|
|
11
|
+
* Where a password reset link points, and the email that carries it. The raw
|
|
12
|
+
* token in a link is a credential for the account until it expires or is
|
|
13
|
+
* used: never log the result.
|
|
10
14
|
*
|
|
11
15
|
* The token goes in the fragment (#), not the path. Browsers never send the
|
|
12
16
|
* fragment to the server, so opening the link can't put the token in any
|
|
13
17
|
* server-side log. As a path segment it was logged on every open: Appium's
|
|
14
18
|
* [HTTP] request line and Xenon's UI-fallback line both record the URL.
|
|
15
19
|
*/
|
|
16
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The address a link Xenon sends (by email, or to the log fallback) points
|
|
22
|
+
* at: this server's address from XENON_PUBLIC_URL (publicServerBase), or
|
|
23
|
+
* null when it is unset or not a usable address.
|
|
24
|
+
*
|
|
25
|
+
* Never the request's Host header. Through 2.14 the link was built from it,
|
|
26
|
+
* so anyone could ask for a reset of someone else's account naming a host of
|
|
27
|
+
* their own (a matching Origin passes the CSRF check), and Xenon emailed the
|
|
28
|
+
* victim a genuine link to that host, which handed over the token.
|
|
29
|
+
*/
|
|
30
|
+
function resetLinkBase(cfg = config_1.config) {
|
|
31
|
+
return (0, publicUrl_1.publicServerBase)(cfg);
|
|
32
|
+
}
|
|
33
|
+
/** The reset page for `rawToken`, under `base` (resetLinkBase, or requestBase). */
|
|
34
|
+
function buildResetLink(base, rawToken) {
|
|
35
|
+
return `${base}/xenon/reset-password#${rawToken}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The address the caller reached this server on, honouring a TLS-terminating
|
|
39
|
+
* proxy's x-forwarded-proto. Only for a link handed back to the signed-in
|
|
40
|
+
* administrator who asked for it (POST /users/:id/reset-link when it can't be
|
|
41
|
+
* emailed): they see where it points before passing it on, and hold the token
|
|
42
|
+
* anyway. Never for a link Xenon sends to someone else.
|
|
43
|
+
*/
|
|
44
|
+
function requestBase(req) {
|
|
17
45
|
const proto = req.secure || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http';
|
|
18
46
|
const host = req.headers.host || 'localhost';
|
|
19
|
-
return `${proto}://${host}
|
|
47
|
+
return `${proto}://${host}`;
|
|
48
|
+
}
|
|
49
|
+
/** "1 hour", "30 minutes", "2 hours": a reset link's lifetime, for the email. */
|
|
50
|
+
function describeLifetime(ms) {
|
|
51
|
+
const minutes = Math.max(1, Math.round(ms / 60000));
|
|
52
|
+
if (minutes % 60 === 0) {
|
|
53
|
+
const hours = minutes / 60;
|
|
54
|
+
return `${hours} hour${hours === 1 ? '' : 's'}`;
|
|
55
|
+
}
|
|
56
|
+
return `${minutes} minute${minutes === 1 ? '' : 's'}`;
|
|
20
57
|
}
|
|
21
|
-
/**
|
|
22
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The reset email, shared by self-service and admin-issued resets. `ttlMs` is
|
|
60
|
+
* how long the link lasts (PasswordResetService.ttlMs(), from
|
|
61
|
+
* XENON_RESET_TOKEN_TTL_MS): the email said "1 hour" whatever it was.
|
|
62
|
+
*/
|
|
63
|
+
function resetEmail(user, link, ttlMs) {
|
|
23
64
|
return {
|
|
24
65
|
to: user.email,
|
|
25
66
|
subject: 'Reset your Xenon password',
|
|
@@ -27,6 +68,6 @@ function resetEmail(user, link) {
|
|
|
27
68
|
`Someone requested a password reset for your Xenon account. ` +
|
|
28
69
|
`If that was you, click the link below to choose a new password:\n\n` +
|
|
29
70
|
`${link}\n\n` +
|
|
30
|
-
`This link expires in
|
|
71
|
+
`This link expires in ${describeLifetime(ttlMs)}. If you did not request this, ignore this email — no action is needed.\n`,
|
|
31
72
|
};
|
|
32
73
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.passwordResetMode = passwordResetMode;
|
|
4
|
+
const passwordResetLink_1 = require("./passwordResetLink");
|
|
4
5
|
function passwordResetMode(cfg) {
|
|
5
|
-
|
|
6
|
+
const smtp = !!(cfg.smtpUrl && cfg.smtpUrl.trim());
|
|
7
|
+
return smtp && (0, passwordResetLink_1.resetLinkBase)(cfg) !== null ? 'email' : 'admin';
|
|
6
8
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.publicServerBase = publicServerBase;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
/**
|
|
6
|
+
* XENON_PUBLIC_URL: the address people reach this Xenon server at, such as
|
|
7
|
+
* https://xenon.example.com or http://lab-mac:4723. Links Xenon hands out
|
|
8
|
+
* (password reset links, a device's dashboard_link) are built from it, never
|
|
9
|
+
* from the request's Host header, which whoever sends the request chooses.
|
|
10
|
+
*
|
|
11
|
+
* The dashboard's own address is taken too: a trailing `/xenon` or `/xenon/`
|
|
12
|
+
* is dropped, so `http://localhost:4723/xenon/` gives `http://localhost:4723`.
|
|
13
|
+
* Kept as given it built `/xenon/xenon/reset-password`, which the dashboard
|
|
14
|
+
* sends to its sign-in page, losing the token.
|
|
15
|
+
*
|
|
16
|
+
* Any other path is refused: the dashboard loads its pages, scripts and API
|
|
17
|
+
* from `/xenon` on the server's root (its base path is fixed at build time),
|
|
18
|
+
* so it can't be served under a reverse-proxy prefix, and a link under one
|
|
19
|
+
* wouldn't open. So are an address that isn't http(s), and one with a query,
|
|
20
|
+
* fragment or user name (a fragment would swallow a reset token).
|
|
21
|
+
*
|
|
22
|
+
* Returns the origin (scheme, host and port, no trailing slash), or null when
|
|
23
|
+
* the variable is unset or not such an address.
|
|
24
|
+
*/
|
|
25
|
+
function publicServerBase(cfg = config_1.config) {
|
|
26
|
+
var _a;
|
|
27
|
+
const raw = (_a = cfg.publicUrl) === null || _a === void 0 ? void 0 : _a.trim();
|
|
28
|
+
if (!raw)
|
|
29
|
+
return null;
|
|
30
|
+
let url;
|
|
31
|
+
try {
|
|
32
|
+
url = new URL(raw);
|
|
33
|
+
}
|
|
34
|
+
catch (_b) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
38
|
+
return null;
|
|
39
|
+
if (url.search || url.hash || url.username || url.password)
|
|
40
|
+
return null;
|
|
41
|
+
const path = url.pathname.replace(/\/+$/, '');
|
|
42
|
+
if (path !== '' && path !== '/xenon')
|
|
43
|
+
return null;
|
|
44
|
+
return url.origin;
|
|
45
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
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 __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.SelectorVisibilityResolver = exports.SELECTOR_VISIBILITY_LOOKUP_TIMEOUT_MS = exports.SELECTOR_VISIBILITY_TTL_MS = void 0;
|
|
25
|
+
const typedi_1 = require("typedi");
|
|
26
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
27
|
+
const sessionVisibility_1 = require("../device-access/sessionVisibility");
|
|
28
|
+
const access_1 = require("./access");
|
|
29
|
+
/**
|
|
30
|
+
* How long an answer is reused. A selector event is rare, but a verification
|
|
31
|
+
* run can send several at once, each to every dashboard connected.
|
|
32
|
+
*/
|
|
33
|
+
exports.SELECTOR_VISIBILITY_TTL_MS = 5000;
|
|
34
|
+
/** A lookup that takes longer answers no for that event, and isn't cached. */
|
|
35
|
+
exports.SELECTOR_VISIBILITY_LOOKUP_TIMEOUT_MS = 2000;
|
|
36
|
+
const MAX_ENTRIES = 10000;
|
|
37
|
+
/**
|
|
38
|
+
* Whether a dashboard caller may see a selector, for the `selector_*` live
|
|
39
|
+
* events: Selector Health's own rule (`access.ts`), a selector healed in at
|
|
40
|
+
* least one session the caller may see. Admins see every selector and are
|
|
41
|
+
* never asked about.
|
|
42
|
+
*
|
|
43
|
+
* Answers are cached for {@link SELECTOR_VISIBILITY_TTL_MS} per viewer (user
|
|
44
|
+
* and teams, so a member's tabs share one) and selector, and a viewer's
|
|
45
|
+
* sessions are read once for all their selectors. Concurrent questions share
|
|
46
|
+
* one lookup. A lookup that fails, or outlasts
|
|
47
|
+
* {@link SELECTOR_VISIBILITY_LOOKUP_TIMEOUT_MS}, answers no and isn't cached.
|
|
48
|
+
*/
|
|
49
|
+
let SelectorVisibilityResolver = class SelectorVisibilityResolver {
|
|
50
|
+
// An interface-typed parameter emits `Object`, which TypeDI leaves alone; a
|
|
51
|
+
// function-typed one would make Container.get throw.
|
|
52
|
+
constructor(deps = {}) {
|
|
53
|
+
this.deps = deps;
|
|
54
|
+
this.scopes = new Map();
|
|
55
|
+
this.answers = new Map();
|
|
56
|
+
}
|
|
57
|
+
/** Never rejects. */
|
|
58
|
+
canSee(key, viewer) {
|
|
59
|
+
var _a;
|
|
60
|
+
const who = JSON.stringify([(_a = viewer.userId) !== null && _a !== void 0 ? _a : null, Array.from(new Set(viewer.teamIds)).sort()]);
|
|
61
|
+
const answer = this.cached(this.answers, JSON.stringify([who, key.strategy, key.selector]), () => __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
const scope = yield this.cached(this.scopes, who, () => this.sessionScope(viewer));
|
|
63
|
+
return this.healedIn(key, scope);
|
|
64
|
+
}));
|
|
65
|
+
return answer.catch((err) => {
|
|
66
|
+
var _a;
|
|
67
|
+
logger_1.default.warn(`[SelectorVisibility] could not tell who may see ${key.strategy}=${key.selector}: ` +
|
|
68
|
+
`${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}; its event went to admins only`);
|
|
69
|
+
return false;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
cached(map, key, load) {
|
|
73
|
+
const entry = map.get(key);
|
|
74
|
+
if (entry &&
|
|
75
|
+
(entry.settledAt === undefined || this.now() - entry.settledAt < exports.SELECTOR_VISIBILITY_TTL_MS)) {
|
|
76
|
+
return entry.value;
|
|
77
|
+
}
|
|
78
|
+
const fresh = { value: Promise.resolve(undefined) };
|
|
79
|
+
fresh.value = this.bounded(load).then((value) => {
|
|
80
|
+
fresh.settledAt = this.now();
|
|
81
|
+
return value;
|
|
82
|
+
}, (err) => {
|
|
83
|
+
if (map.get(key) === fresh)
|
|
84
|
+
map.delete(key);
|
|
85
|
+
throw err;
|
|
86
|
+
});
|
|
87
|
+
if (map.size >= MAX_ENTRIES && !map.has(key))
|
|
88
|
+
map.clear();
|
|
89
|
+
map.set(key, fresh);
|
|
90
|
+
return fresh.value;
|
|
91
|
+
}
|
|
92
|
+
bounded(load) {
|
|
93
|
+
var _a;
|
|
94
|
+
const timeoutMs = (_a = this.deps.lookupTimeoutMs) !== null && _a !== void 0 ? _a : exports.SELECTOR_VISIBILITY_LOOKUP_TIMEOUT_MS;
|
|
95
|
+
let timer;
|
|
96
|
+
const timedOut = new Promise((_, reject) => {
|
|
97
|
+
var _a;
|
|
98
|
+
timer = setTimeout(() => reject(new Error(`took over ${timeoutMs} ms`)), timeoutMs);
|
|
99
|
+
(_a = timer.unref) === null || _a === void 0 ? void 0 : _a.call(timer);
|
|
100
|
+
});
|
|
101
|
+
const lookup = new Promise((resolve) => resolve(load()));
|
|
102
|
+
return Promise.race([lookup, timedOut]).finally(() => clearTimeout(timer));
|
|
103
|
+
}
|
|
104
|
+
sessionScope(viewer) {
|
|
105
|
+
if (this.deps.sessionScope)
|
|
106
|
+
return this.deps.sessionScope(viewer);
|
|
107
|
+
return (0, sessionVisibility_1.visibleSessionWhere)(viewer);
|
|
108
|
+
}
|
|
109
|
+
healedIn(key, scope) {
|
|
110
|
+
var _a;
|
|
111
|
+
return ((_a = this.deps.healedIn) !== null && _a !== void 0 ? _a : access_1.canSeeSelector)(key, scope);
|
|
112
|
+
}
|
|
113
|
+
now() {
|
|
114
|
+
return this.deps.now ? this.deps.now() : Date.now();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
exports.SelectorVisibilityResolver = SelectorVisibilityResolver;
|
|
118
|
+
exports.SelectorVisibilityResolver = SelectorVisibilityResolver = __decorate([
|
|
119
|
+
(0, typedi_1.Service)(),
|
|
120
|
+
__metadata("design:paramtypes", [Object])
|
|
121
|
+
], SelectorVisibilityResolver);
|
|
@@ -21,9 +21,11 @@ function resolveSessionIdentity(input) {
|
|
|
21
21
|
if (!input.sessionToken)
|
|
22
22
|
return NONE;
|
|
23
23
|
// Attribution is decoupled from enforcement: a token that does not verify is
|
|
24
|
-
// IGNORED here, never rejected.
|
|
25
|
-
//
|
|
26
|
-
//
|
|
24
|
+
// IGNORED here, never rejected. Whether the session is admitted is decided
|
|
25
|
+
// by the caller: assertSessionTokenGate for a session with no valid
|
|
26
|
+
// credentials, and authorizeSessionRequest's `sessions` scope check for a
|
|
27
|
+
// token that verifies. That separation is what lets a valid token identify
|
|
28
|
+
// its caller even when the gate is switched off.
|
|
27
29
|
try {
|
|
28
30
|
const payload = yield input.verify(input.sessionToken);
|
|
29
31
|
const sub = payload === null || payload === void 0 ? void 0 : payload.sub;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UNEXPECTED_SHUTDOWN_REASON = void 0;
|
|
4
|
+
exports.unexpectedShutdownReason = unexpectedShutdownReason;
|
|
5
|
+
/** The reason recorded when Appium ends a session and gives no cause of its own. */
|
|
6
|
+
exports.UNEXPECTED_SHUTDOWN_REASON = 'Driver shut down unexpectedly';
|
|
7
|
+
/**
|
|
8
|
+
* The failure reason of a session Appium ended by itself
|
|
9
|
+
* (`XenonPlugin.onUnexpectedShutdown`): the message of the cause Appium
|
|
10
|
+
* passed, else UNEXPECTED_SHUTDOWN_REASON.
|
|
11
|
+
*
|
|
12
|
+
* Appium's new-command timeout is the usual cause, and its message ("New
|
|
13
|
+
* Command Timeout of 60 seconds expired. Try customizing the timeout using
|
|
14
|
+
* the 'newCommandTimeout' desired capability") is what files the session
|
|
15
|
+
* under TIMEOUT. A driver that gives up on itself passes its own error, such
|
|
16
|
+
* as base-driver's "The driver was unexpectedly shut down!". The umbrella's
|
|
17
|
+
* placeholder for a missing cause, "Unknown error", says nothing, so it gets
|
|
18
|
+
* the fallback too. Through 2.14 every such session was recorded with the
|
|
19
|
+
* fixed reason, which no failure pattern matches, so an idle session was
|
|
20
|
+
* filed UNKNOWN.
|
|
21
|
+
*/
|
|
22
|
+
function unexpectedShutdownReason(cause) {
|
|
23
|
+
const message = typeof cause === 'string' ? cause : cause === null || cause === void 0 ? void 0 : cause.message;
|
|
24
|
+
const text = typeof message === 'string' ? message.trim() : '';
|
|
25
|
+
return text && text !== 'Unknown error' ? text : exports.UNEXPECTED_SHUTDOWN_REASON;
|
|
26
|
+
}
|