@xenon-device-management/xenon 2.13.2 → 2.15.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 +17 -8
- package/lib/package.json +5 -5
- package/lib/public/assets/{AnnotationOverlay-BXruClDk.js → AnnotationOverlay-Btq3dZRI.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-qTS4LPLC.js → ApiKeyGate-C9OoAgg_.js} +1 -1
- package/lib/public/assets/DeviceMosaicView-DBC_xczB.js +2 -0
- package/lib/public/assets/{EmptyState-Cr17PdDv.js → EmptyState-DqWU2-vM.js} +1 -1
- package/lib/public/assets/{FieldGroup-Bvn08svv.js → FieldGroup-CveQ3xrA.js} +1 -1
- package/lib/public/assets/{FilterMenu-DDSO8aDf.js → FilterMenu-BfKDSfHz.js} +1 -1
- package/lib/public/assets/{Menu-B-Qn2X3T.js → Menu-CtdCybzA.js} +1 -1
- package/lib/public/assets/{Modal-Cg9V5JYU.js → Modal-Bsvj1tm2.js} +1 -1
- package/lib/public/assets/{RecordingPage-BTUs9VTW.js → RecordingPage-CRofOiyx.js} +1 -1
- package/lib/public/assets/{RecordingsPage-DYcfxITK.js → RecordingsPage-jNQHb2IR.js} +2 -2
- package/lib/public/assets/{SegmentedControl-Zy7suhT5.js → SegmentedControl-C8HtTha-.js} +1 -1
- package/lib/public/assets/{SettingCard-CWYmycoe.js → SettingCard-D8K1LZNo.js} +1 -1
- package/lib/public/assets/{Table-io9fwa98.js → Table-zNTX92Pn.js} +1 -1
- package/lib/public/assets/{activity-Bdf_uIWl.js → activity-CJ8LwZq9.js} +1 -1
- package/lib/public/assets/ai-settings-CgxQZqrc.js +16 -0
- package/lib/public/assets/{api-keys-CPqtVpm3.js → api-keys-ECpo-zTo.js} +1 -1
- package/lib/public/assets/{apps-BgZan0H9.js → apps-BjGymyf3.js} +2 -2
- package/lib/public/assets/{arrow-left-B27ckB31.js → arrow-left-BWfCbeHP.js} +1 -1
- package/lib/public/assets/{arrow-right-DOF8ITCz.js → arrow-right-DC7UGf_n.js} +1 -1
- package/lib/public/assets/{arrow-up-right-eFG5KTFS.js → arrow-up-right-BOjwybDk.js} +1 -1
- package/lib/public/assets/{auth-shell-DlC3_27j.js → auth-shell-CxMTo9aH.js} +2 -2
- package/lib/public/assets/{builds-page-BuXpPfKo.js → builds-page-DOCm4HkA.js} +1 -1
- package/lib/public/assets/{button-fIW8owMI.js → button-Cg08-EMT.js} +1 -1
- package/lib/public/assets/{calendar-WNjmrF-a.js → calendar-CA1ikLJs.js} +1 -1
- package/lib/public/assets/{check-BdTh1Sq7.js → check-BMTaH72W.js} +1 -1
- package/lib/public/assets/{chevron-right-B4BKlsqk.js → chevron-right-B2QOv9Nx.js} +1 -1
- package/lib/public/assets/{circle-check-zJU80_EF.js → circle-check-CYXBoUqT.js} +1 -1
- package/lib/public/assets/{circle-x-5Uwquo0h.js → circle-x-DTwnSAnl.js} +1 -1
- package/lib/public/assets/{clock-BtUprDsZ.js → clock-C8_HViQP.js} +1 -1
- package/lib/public/assets/{copy-DGSbZI3u.js → copy-Be7LNMXV.js} +1 -1
- package/lib/public/assets/derive-UnLRp4r3.js +1 -0
- package/lib/public/assets/device-explorer-BOqtfGXz.js +282 -0
- package/lib/public/assets/{download-WG6l7BgD.js → download-B56lwxWP.js} +1 -1
- package/lib/public/assets/forgot-password-CCU6bzLn.js +1 -0
- package/lib/public/assets/h264Stream-C-OtmIuk.js +26 -0
- package/lib/public/assets/index-B9KQ6LsY.js +15 -0
- package/lib/public/assets/{index-DI1fGIyj.js → index-BOxe43p1.js} +5 -5
- package/lib/public/assets/{index-HDQVe1gi.js → index-CfxsUslp.js} +1 -1
- package/lib/public/assets/{input-BjDRKCea.js → input-CZHFjbSg.js} +1 -1
- package/lib/public/assets/{line-chart-C0zdgd0v.js → line-chart-Gl_Dz3X_.js} +1 -1
- package/lib/public/assets/{list-checks-BdW5CjM8.js → list-checks-CCgjeyGY.js} +1 -1
- package/lib/public/assets/{lock-Dy3L4I8E.js → lock-CkOwgDcx.js} +1 -1
- package/lib/public/assets/{login-BHbY9CLG.js → login-D5cc4w7M.js} +1 -1
- package/lib/public/assets/maintenance-settings-t7qr3fvX.js +6 -0
- package/lib/public/assets/{monitor-8xjkeHHi.js → monitor-DEJixKQO.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-CXdKMXQ8.js → mouse-pointer-2-CtOrk4M8.js} +1 -1
- package/lib/public/assets/{network-Cj8JOKON.js → network-Dii9qdvk.js} +1 -1
- package/lib/public/assets/{overview-CTg7F0im.js → overview-rolAACrO.js} +1 -1
- package/lib/public/assets/{page-header--aTyJXq6.js → page-header-COQZxJfR.js} +1 -1
- package/lib/public/assets/{play-B-FodgHV.js → play-DrN1Se_R.js} +1 -1
- package/lib/public/assets/{plus-CsIk10Cq.js → plus-5aq-9uRx.js} +1 -1
- package/lib/public/assets/{profile-page-DkFKkMfl.js → profile-page-6i5kY6kF.js} +1 -1
- package/lib/public/assets/{reset-password-I2-zqLlI.js → reset-password-sp2IBrc7.js} +1 -1
- package/lib/public/assets/runbook-page-DJDjeopU.js +253 -0
- package/lib/public/assets/{select-DvCSv25z.js → select-Bpyn7rWO.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BLVfMJs_.js → selector-detail-redirect-BH74vmKv.js} +1 -1
- package/lib/public/assets/{selector-health-page-oqadRokn.js → selector-health-page-Cw_g218H.js} +2 -2
- package/lib/public/assets/session-detail-page-AZJE6XXE.js +4 -0
- package/lib/public/assets/settings-C3uyGhyc.js +6 -0
- package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
- package/lib/public/assets/{stat-tile-DV9Ix-4O.js → stat-tile-xVqAKIlI.js} +1 -1
- package/lib/public/assets/{tablet-6b_SEN2X.js → tablet-BxUScAeO.js} +1 -1
- package/lib/public/assets/{teams-BjZyfuHh.js → teams-B5ljGkY2.js} +1 -1
- package/lib/public/assets/{trash-2-CTZpPSdd.js → trash-2-B701i8sy.js} +1 -1
- package/lib/public/assets/{upload-P3XzwQUb.js → upload-C2gr44RS.js} +1 -1
- package/lib/public/assets/{use-builds-data-1cOCMf-z.js → use-builds-data-NeQOFEoy.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-DgF9w088.js → use-password-reset-mode-DEzJq5ef.js} +1 -1
- package/lib/public/assets/{users-DMF9Mk5H.js → users-D_vv_yz4.js} +1 -1
- package/lib/public/assets/{users-CiNTKUiz.js → users-MY6eNe6s.js} +1 -1
- package/lib/public/assets/{video-off-CHp1KU_T.js → video-off-OK1lNZQn.js} +1 -1
- package/lib/public/assets/webhook-settings-BU-C9lBf.js +1 -0
- package/lib/public/assets/{zap-DRz0tDq5.js → zap-DO9ry1iz.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +23 -25
- package/lib/src/XenonCapabilityManager.js +22 -5
- package/lib/src/app/dashboardPluginLink.js +117 -0
- package/lib/src/app/index.js +13 -34
- package/lib/src/app/openapi/control.yaml +73 -20
- package/lib/src/app/openapi/grid.yaml +31 -7
- package/lib/src/app/openapi/identity.yaml +103 -31
- package/lib/src/app/openapi/platform.yaml +165 -39
- package/lib/src/app/openapi/sessions.yaml +10 -3
- package/lib/src/app/routers/apikeys.js +14 -2
- package/lib/src/app/routers/auth.js +29 -5
- package/lib/src/app/routers/control.js +20 -2
- package/lib/src/app/routers/dashboard.js +69 -26
- package/lib/src/app/routers/grid.js +37 -13
- package/lib/src/app/routers/profile.js +21 -0
- package/lib/src/app/routers/streamType.js +7 -2
- package/lib/src/app/routers/users.js +10 -5
- package/lib/src/app/routers/webhook.js +8 -4
- package/lib/src/app/swagger.js +2 -0
- package/lib/src/app/ws/h264StreamWs.js +10 -1
- package/lib/src/config.js +51 -5
- package/lib/src/dashboard/commands.js +185 -141
- package/lib/src/dashboard/event-manager.js +88 -67
- package/lib/src/dashboard/services/failure-analysis-service.js +136 -45
- package/lib/src/dashboard/sessionCommandSummary.js +39 -0
- package/lib/src/data-service/appleFamily.js +43 -0
- package/lib/src/data-service/db.js +11 -0
- package/lib/src/data-service/device-service.js +6 -1
- package/lib/src/data-service/device-store.js +76 -3
- package/lib/src/data-service/deviceClaims.js +37 -0
- package/lib/src/data-service/deviceFieldOwners.js +25 -11
- package/lib/src/data-service/deviceSettings.js +79 -0
- package/lib/src/data-service/prisma-store.js +169 -15
- package/lib/src/data-service/web-config-service.js +50 -92
- package/lib/src/device-managers/AndroidDeviceManager.js +9 -31
- package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
- package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
- package/lib/src/device-managers/ios/WDAClient.js +44 -43
- package/lib/src/device-managers/shellCommands.js +96 -0
- package/lib/src/device-utils.js +114 -40
- package/lib/src/gateway/defaultGateway.js +1 -1
- package/lib/src/gateway/forwardToNode.js +2 -0
- package/lib/src/gateway/healReport.js +87 -0
- package/lib/src/gateway/sessionGateway.js +12 -2
- package/lib/src/generated/client/edge.js +18 -3
- package/lib/src/generated/client/index-browser.js +15 -0
- package/lib/src/generated/client/index.d.ts +2086 -379
- package/lib/src/generated/client/index.js +18 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +38 -7
- package/lib/src/generated/client/wasm.js +15 -0
- package/lib/src/interceptors/CommandInterceptor.js +271 -188
- package/lib/src/interceptors/xenonScripts.js +55 -0
- package/lib/src/interfaces/IPluginArgs.js +1 -2
- 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 +23 -4
- package/lib/src/prisma.js +1 -1
- package/lib/src/scripts/database-check.js +124 -0
- package/lib/src/services/AICommandService.js +9 -2
- package/lib/src/services/AIService.js +281 -32
- package/lib/src/services/CleanupService.js +6 -1
- package/lib/src/services/EmailService.js +29 -0
- package/lib/src/services/InterceptorService.js +70 -13
- package/lib/src/services/NetworkConditioningService.js +85 -88
- package/lib/src/services/NotificationService.js +36 -29
- package/lib/src/services/OrphanSweeper.js +7 -0
- package/lib/src/services/PortAllocator.js +5 -0
- package/lib/src/services/SelectorVerificationJob.js +15 -4
- package/lib/src/services/ServerManager.js +105 -9
- package/lib/src/services/SessionLifecycleService.js +133 -75
- package/lib/src/services/ShutdownCoordinator.js +33 -0
- package/lib/src/services/SocketServer.js +19 -3
- package/lib/src/services/TeamService.js +20 -3
- 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 +85 -71
- 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/interceptor/AndroidProxyAdapter.js +18 -0
- package/lib/src/services/interceptor/interceptorOptions.js +44 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +271 -0
- package/lib/src/services/logcat/deviceLogBook.js +163 -0
- package/lib/src/services/network/AndroidRadios.js +71 -0
- package/lib/src/services/network/PhoneNetworkLedger.js +184 -0
- package/lib/src/services/network/PhoneNetworkRestore.js +330 -0
- package/lib/src/services/network/adbForPhone.js +60 -0
- package/lib/src/services/network/phoneNetworkLock.js +20 -0
- package/lib/src/services/network/xenonProxy.js +107 -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 +132 -95
- package/lib/src/services/omni-vision/VisionAssertionService.js +28 -25
- package/lib/src/services/omni-vision/ocrTextMatch.js +105 -0
- package/lib/src/services/omni-vision/screenScale.js +68 -0
- 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/recording/concurrency-gate.js +12 -3
- package/lib/src/services/session/sessionIdentity.js +5 -3
- package/lib/src/services/session/shutdownReason.js +26 -0
- package/lib/src/services/settings/SelfHealingSwitch.js +70 -0
- package/lib/src/services/settings/aiEngineSettings.js +219 -0
- package/lib/src/services/settings/labSettings.js +149 -0
- package/lib/src/services/token/mintedLifetime.js +51 -0
- package/lib/src/services/webhookEvents.js +173 -0
- package/lib/src/sessions/SessionManager.js +8 -2
- package/lib/src/sessions/sessionMemory.js +25 -0
- package/lib/test/helpers/fake-adb.js +58 -0
- package/lib/test/helpers/fake-ai-provider.js +188 -0
- package/lib/test/helpers/fake-device-table.js +63 -24
- package/lib/test/helpers/loki-stores.js +33 -0
- package/lib/test/helpers/page-sources.js +164 -0
- package/lib/test/helpers/scratch-database.js +58 -13
- 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/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/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/TeamService.test.js +3 -0
- package/lib/test/unit/XenonCapabilityManager.spec.js +4 -2
- 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-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/apple-family.spec.js +36 -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/cleanup-settings-db.spec.js +103 -0
- package/lib/test/unit/cleanup-settings.spec.js +244 -0
- package/lib/test/unit/dashboard-api-paths.spec.js +101 -0
- package/lib/test/unit/dashboard-commands.spec.js +249 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +207 -0
- package/lib/test/unit/dashboard-events-team-scope.spec.js +9 -6
- package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
- package/lib/test/unit/database-check.spec.js +108 -0
- package/lib/test/unit/default-plugin-args.spec.js +33 -0
- package/lib/test/unit/device-log-book.spec.js +180 -0
- package/lib/test/unit/device-offline-webhook.spec.js +81 -0
- package/lib/test/unit/device-setting-columns.spec.js +66 -0
- package/lib/test/unit/device-settings-loki.spec.js +87 -0
- package/lib/test/unit/device-settings-survive.spec.js +555 -0
- package/lib/test/unit/device-sync-lost-update.spec.js +5 -0
- package/lib/test/unit/emulator-boot.spec.js +91 -0
- package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
- package/lib/test/unit/event-log-session-data.spec.js +244 -0
- package/lib/test/unit/failure-analysis.spec.js +182 -0
- package/lib/test/unit/failure-categories.spec.js +128 -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 +73 -29
- package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +219 -0
- package/lib/test/unit/hub-node-same-host.spec.js +5 -0
- package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
- package/lib/test/unit/iphone-ipad-only.spec.js +132 -0
- package/lib/test/unit/json-logging-option.spec.js +74 -0
- package/lib/test/unit/lab-settings.spec.js +218 -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/max-sessions.spec.js +240 -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/ocr-text-match.spec.js +44 -0
- package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
- package/lib/test/unit/omni-vision-analyze-screen.spec.js +72 -0
- package/lib/test/unit/omni-vision-ios-points.spec.js +159 -0
- package/lib/test/unit/omni-vision-tap-text.spec.js +129 -0
- package/lib/test/unit/omni-vision.spec.js +6 -8
- package/lib/test/unit/package-engines.spec.js +27 -0
- package/lib/test/unit/partial-config-file.spec.js +14 -0
- package/lib/test/unit/passwordResetMode.test.js +10 -2
- package/lib/test/unit/phone-network/boot-clean-up.spec.js +64 -0
- package/lib/test/unit/phone-network/interceptor-options.spec.js +77 -0
- package/lib/test/unit/phone-network/interceptor-proxy-restore.spec.js +145 -0
- package/lib/test/unit/phone-network/network-conditioning.spec.js +145 -0
- package/lib/test/unit/phone-network/phone-network-restore.spec.js +246 -0
- package/lib/test/unit/phone-network/session-end-paths.spec.js +182 -0
- package/lib/test/unit/phone-network/session-start.spec.js +110 -0
- package/lib/test/unit/phone-network/xenon-proxy.spec.js +69 -0
- package/lib/test/unit/plugin.spec.js +3 -3
- package/lib/test/unit/profile-router.test.js +122 -1
- package/lib/test/unit/queue-requester.spec.js +1 -1
- package/lib/test/unit/recording-options.spec.js +181 -0
- 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/selector-learning-every-session.spec.js +179 -0
- package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
- package/lib/test/unit/selector-verification-job.spec.js +13 -13
- package/lib/test/unit/self-healing-boot.spec.js +80 -0
- package/lib/test/unit/self-healing-setting.spec.js +273 -0
- 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.spec.js +347 -0
- package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
- package/lib/test/unit/session-failed-webhook.spec.js +170 -0
- package/lib/test/unit/session-log-indexes.spec.js +64 -3
- package/lib/test/unit/shell-commands.spec.js +133 -0
- package/lib/test/unit/socketServer.scopedEmit.spec.js +2 -0
- package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
- package/lib/test/unit/stream-start-pipelines.spec.js +264 -0
- package/lib/test/unit/stream-status-type.spec.js +4 -0
- package/lib/test/unit/token-mint-limits.spec.js +165 -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 +119 -0
- package/lib/test/unit/wda-client-shell.spec.js +133 -0
- package/lib/test/unit/web-config-service.spec.js +98 -0
- package/lib/test/unit/webhook-delivery.spec.js +24 -0
- package/lib/test/unit/webhook-payloads.spec.js +273 -0
- package/lib/test/unit/xenon-execute-scripts.spec.js +138 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/web/src/components/webhook-settings/webhookEventVariables.json +30 -0
- package/package.json +5 -5
- package/prisma/migrations/20261004140000_keep_device_settings/migration.sql +19 -0
- package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
- package/prisma/schema.prisma +38 -7
- package/schema.json +23 -25
- package/scripts/generate-types-from-schema.js +1 -2
- package/scripts/lib/always-present-plugin-args.js +4 -1
- package/lib/public/assets/BugReportButton-BoGhlI7F.js +0 -15
- package/lib/public/assets/DeviceMosaicView-8J0oYqMM.js +0 -2
- package/lib/public/assets/ai-settings-Bce5SJob.js +0 -21
- package/lib/public/assets/device-explorer-C-KnhMHI.js +0 -279
- package/lib/public/assets/forgot-password-DnudqFFo.js +0 -1
- package/lib/public/assets/maintenance-settings-zDVlOMVK.js +0 -6
- package/lib/public/assets/recording-group-store-WVM3hoXn.js +0 -26
- package/lib/public/assets/runbook-page-DJTVCJX7.js +0 -124
- package/lib/public/assets/session-detail-page-CGn55Pvl.js +0 -4
- package/lib/public/assets/settings-MsVU2ukE.js +0 -6
- package/lib/public/assets/webhook-settings-DiS8geIm.js +0 -1
- /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
package/lib/src/device-utils.js
CHANGED
|
@@ -57,7 +57,7 @@ exports.initializeStorage = initializeStorage;
|
|
|
57
57
|
exports.getUtilizationTime = getUtilizationTime;
|
|
58
58
|
exports.setUtilizationTime = setUtilizationTime;
|
|
59
59
|
exports.getDeviceFiltersFromCapability = getDeviceFiltersFromCapability;
|
|
60
|
-
exports.
|
|
60
|
+
exports.getRunningSessionsCount = getRunningSessionsCount;
|
|
61
61
|
exports.updateDeviceList = updateDeviceList;
|
|
62
62
|
exports.refreshSimulatorState = refreshSimulatorState;
|
|
63
63
|
exports.setupCronCheckStaleDevices = setupCronCheckStaleDevices;
|
|
@@ -72,6 +72,7 @@ exports.cleanPendingSessions = cleanPendingSessions;
|
|
|
72
72
|
exports.setupCronCleanPendingSessions = setupCronCleanPendingSessions;
|
|
73
73
|
exports.setupCronCleanExpiredReservations = setupCronCleanExpiredReservations;
|
|
74
74
|
exports.setupCronCleanupBuilds = setupCronCleanupBuilds;
|
|
75
|
+
exports.rescheduleCleanupBuilds = rescheduleCleanupBuilds;
|
|
75
76
|
exports.setupCronReconcileDevices = setupCronReconcileDevices;
|
|
76
77
|
exports.setupCronSweepOrphanSessions = setupCronSweepOrphanSessions;
|
|
77
78
|
exports.setupCronSelectorVerification = setupCronSelectorVerification;
|
|
@@ -80,6 +81,7 @@ exports.stopAllTimers = stopAllTimers;
|
|
|
80
81
|
const helpers_1 = require("./helpers");
|
|
81
82
|
const XenonCapabilityManager_1 = require("./XenonCapabilityManager");
|
|
82
83
|
const async_wait_until_1 = __importDefault(require("async-wait-until"));
|
|
84
|
+
const async_lock_1 = __importDefault(require("async-lock"));
|
|
83
85
|
const typedi_1 = require("typedi");
|
|
84
86
|
const device_managers_1 = require("./device-managers");
|
|
85
87
|
const device_service_1 = require("./data-service/device-service");
|
|
@@ -152,6 +154,9 @@ function isDeviceConfigPathAbsolute(path) {
|
|
|
152
154
|
throw new Error(`Device Config Path ${path} should be absolute`);
|
|
153
155
|
}
|
|
154
156
|
}
|
|
157
|
+
// One at a time through "is there a free slot, and which phone": see allocateDeviceForSession.
|
|
158
|
+
const ALLOCATION_LOCK = new async_lock_1.default();
|
|
159
|
+
const ALLOCATION_LOCK_KEY = 'allocate';
|
|
155
160
|
// What a caller that passes no proof has: nothing. A lease then needs its token.
|
|
156
161
|
const NO_LEASE_PROOF = {
|
|
157
162
|
canOverride: false,
|
|
@@ -212,33 +217,42 @@ function allocateDeviceForSession(capability_1, deviceTimeOutMs_1, deviceQueryIn
|
|
|
212
217
|
yield (0, async_wait_until_1.default)(() => __awaiter(this, void 0, void 0, function* () {
|
|
213
218
|
if (abandoned)
|
|
214
219
|
return false;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
// Get matching devices that are not reserved
|
|
222
|
-
const candidates = (yield (0, device_service_1.getDevices)(filters)).filter((d) => !(0, device_service_1.isDeviceReserved)(d));
|
|
223
|
-
if (candidates.length > 0) {
|
|
224
|
-
// Principal Intelligence: Multi-node consistent locking
|
|
225
|
-
// Atomically claim one of them: busy, with a pending claim for the
|
|
226
|
-
// session being created (deviceClaims.ts). Every candidate, not just
|
|
227
|
-
// the first: the lock skips a phone an SDK lease holds, and offering
|
|
228
|
-
// it alone stalled the create while other phones were free. Exact
|
|
229
|
-
// (udid, host) pairs: a udid repeats across hosts, and a reserved
|
|
230
|
-
// twin of a candidate must not be locked in its place.
|
|
231
|
-
const locked = yield store.findAndLockDevice(Object.assign(Object.assign({}, filters), { udid: [...new Set(candidates.map((d) => d.udid))] }), { claim: true, only: new Set(candidates.map((d) => `${d.udid}@${d.host}`)) });
|
|
232
|
-
if (locked && abandoned) {
|
|
233
|
-
yield (0, device_service_1.releasePendingClaim)(locked);
|
|
220
|
+
// The limit check and the claim are one step. Requests that each read
|
|
221
|
+
// "one slot left" before any of them claimed would all take a phone and
|
|
222
|
+
// run past the limit, and then no later check would see it fixed.
|
|
223
|
+
return ALLOCATION_LOCK.acquire(ALLOCATION_LOCK_KEY, () => __awaiter(this, void 0, void 0, function* () {
|
|
224
|
+
if (abandoned)
|
|
234
225
|
return false;
|
|
226
|
+
const maxSessions = (0, deviceClaims_1.sessionCap)(getDeviceManager().getMaxSessionCount());
|
|
227
|
+
if (maxSessions !== undefined) {
|
|
228
|
+
const running = yield getRunningSessionsCount();
|
|
229
|
+
if (running >= maxSessions) {
|
|
230
|
+
logger_1.default.info(`Waiting for session available, already at max session count of: ${maxSessions}`);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
235
233
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
234
|
+
// Get matching devices that are not reserved
|
|
235
|
+
const candidates = (yield (0, device_service_1.getDevices)(filters)).filter((d) => !(0, device_service_1.isDeviceReserved)(d));
|
|
236
|
+
if (candidates.length > 0) {
|
|
237
|
+
// Principal Intelligence: Multi-node consistent locking
|
|
238
|
+
// Atomically claim one of them: busy, with a pending claim for the
|
|
239
|
+
// session being created (deviceClaims.ts). Every candidate, not just
|
|
240
|
+
// the first: the lock skips a phone an SDK lease holds, and offering
|
|
241
|
+
// it alone stalled the create while other phones were free. Exact
|
|
242
|
+
// (udid, host) pairs: a udid repeats across hosts, and a reserved
|
|
243
|
+
// twin of a candidate must not be locked in its place.
|
|
244
|
+
const locked = yield store.findAndLockDevice(Object.assign(Object.assign({}, filters), { udid: [...new Set(candidates.map((d) => d.udid))] }), { claim: true, only: new Set(candidates.map((d) => `${d.udid}@${d.host}`)) });
|
|
245
|
+
if (locked && abandoned) {
|
|
246
|
+
yield (0, device_service_1.releasePendingClaim)(locked);
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
device = locked;
|
|
250
|
+
if (device !== null)
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
logger_1.default.info(`Waiting for free device. Filter: ${JSON.stringify(filters)}}`);
|
|
254
|
+
return false;
|
|
255
|
+
}));
|
|
242
256
|
}), { timeout, intervalBetweenAttempts });
|
|
243
257
|
}
|
|
244
258
|
catch (err) {
|
|
@@ -446,12 +460,14 @@ function getDeviceFiltersFromCapability(capability, pluginArgs) {
|
|
|
446
460
|
if ((deviceType === null || deviceType === void 0 ? void 0 : deviceType.startsWith('real')) && pluginArgs.iosDeviceType == 'simulated') {
|
|
447
461
|
throw new Error('iosDeviceType value is set to "simulated" but app provided is not suitable for simulator device.');
|
|
448
462
|
}
|
|
449
|
-
|
|
463
|
+
// iPad wins when both are asked for. The device store applies it; it is not
|
|
464
|
+
// a name match (a real phone is named by its owner), see appleFamilyOf.
|
|
465
|
+
let appleFamily = undefined;
|
|
450
466
|
if (capability[customCapability.ipadOnly]) {
|
|
451
|
-
|
|
467
|
+
appleFamily = 'ipad';
|
|
452
468
|
}
|
|
453
469
|
else if (capability[customCapability.iphoneOnly]) {
|
|
454
|
-
|
|
470
|
+
appleFamily = 'iphone';
|
|
455
471
|
}
|
|
456
472
|
// Ensure udid is always an array of strings for the filter
|
|
457
473
|
let udidFilter = [];
|
|
@@ -469,7 +485,6 @@ function getDeviceFiltersFromCapability(capability, pluginArgs) {
|
|
|
469
485
|
platformVersion: capability['appium:platformVersion']
|
|
470
486
|
? capability['appium:platformVersion']
|
|
471
487
|
: undefined,
|
|
472
|
-
name,
|
|
473
488
|
deviceType,
|
|
474
489
|
udid: udidFilter,
|
|
475
490
|
busy: false,
|
|
@@ -481,8 +496,8 @@ function getDeviceFiltersFromCapability(capability, pluginArgs) {
|
|
|
481
496
|
? capability[customCapability.tags].split(',').map(lodash_1.default.trim)
|
|
482
497
|
: undefined,
|
|
483
498
|
};
|
|
484
|
-
if (
|
|
485
|
-
caps = Object.assign(Object.assign({}, caps), {
|
|
499
|
+
if (appleFamily !== undefined) {
|
|
500
|
+
caps = Object.assign(Object.assign({}, caps), { appleFamily });
|
|
486
501
|
}
|
|
487
502
|
return caps;
|
|
488
503
|
}
|
|
@@ -492,12 +507,16 @@ function getDeviceFiltersFromCapability(capability, pluginArgs) {
|
|
|
492
507
|
function getDeviceManager() {
|
|
493
508
|
return typedi_1.Container.get(device_managers_1.XenonManager);
|
|
494
509
|
}
|
|
495
|
-
|
|
510
|
+
/**
|
|
511
|
+
* How many phones run an Appium session, or are being given one: what
|
|
512
|
+
* `maxSessions` limits. A preview, a recording or an idle SDK lease keeps a
|
|
513
|
+
* phone busy without being a session, so counting busy phones would be wrong
|
|
514
|
+
* (see `countsTowardMaxSessions`). On a hub it covers its nodes' phones too.
|
|
515
|
+
*/
|
|
516
|
+
function getRunningSessionsCount() {
|
|
496
517
|
return __awaiter(this, void 0, void 0, function* () {
|
|
497
518
|
const allDevices = yield (0, device_service_1.getAllDevices)();
|
|
498
|
-
return allDevices.filter(
|
|
499
|
-
return device.busy;
|
|
500
|
-
}).length;
|
|
519
|
+
return allDevices.filter(deviceClaims_1.countsTowardMaxSessions).length;
|
|
501
520
|
});
|
|
502
521
|
}
|
|
503
522
|
function updateDeviceList(local, hubArgument, tlsRejectUnauthorized) {
|
|
@@ -629,7 +648,7 @@ function unblockCandidateDevices() {
|
|
|
629
648
|
}
|
|
630
649
|
function releaseBlockedDevices(newCommandTimeout) {
|
|
631
650
|
return __awaiter(this, void 0, void 0, function* () {
|
|
632
|
-
var _a;
|
|
651
|
+
var _a, _b, _c;
|
|
633
652
|
const busyDevices = yield unblockCandidateDevices();
|
|
634
653
|
// A phone an SDK lease holds is the lease's to time out (its heartbeats and
|
|
635
654
|
// expiry, LeaseOrphanSweeper), not idle until a session runs on it. A lease
|
|
@@ -677,6 +696,9 @@ function releaseBlockedDevices(newCommandTimeout) {
|
|
|
677
696
|
if (timeSinceLastCmdExecuted > timeoutSeconds) {
|
|
678
697
|
// unblock regardless of whether the device has session or not
|
|
679
698
|
logger_1.default.info(`Unblocking device ${device.udid} at host ${device.host} because it has been idle for ${timeSinceLastCmdExecuted} seconds`);
|
|
699
|
+
// Before anything below can release the phone (onSessionStopped too).
|
|
700
|
+
yield restoreIdleSessionNetwork((_a = device.claimSessionId) !== null && _a !== void 0 ? _a : device.session_id);
|
|
701
|
+
yield forgetIdleSessionMemory((_b = device.claimSessionId) !== null && _b !== void 0 ? _b : device.session_id);
|
|
680
702
|
// Principal Protection: If this device has an active dashboard session, stop it properly
|
|
681
703
|
if (device.session_id) {
|
|
682
704
|
const sessionId = device.session_id;
|
|
@@ -692,7 +714,7 @@ function releaseBlockedDevices(newCommandTimeout) {
|
|
|
692
714
|
}
|
|
693
715
|
// Keyed on the session read here, so a release that loses the race to
|
|
694
716
|
// the session's own end (and the phone's next claim) frees nothing.
|
|
695
|
-
const claimant = (
|
|
717
|
+
const claimant = (_c = device.claimSessionId) !== null && _c !== void 0 ? _c : device.session_id;
|
|
696
718
|
yield (claimant
|
|
697
719
|
? (0, device_service_1.releaseSessionDevice)(device.udid, device.host, claimant)
|
|
698
720
|
: (0, device_service_1.unblockDevice)(device.udid, device.host)).catch((err) => logger_1.default.error(`Unable to release ${device.udid}: ${err}`));
|
|
@@ -700,6 +722,41 @@ function releaseBlockedDevices(newCommandTimeout) {
|
|
|
700
722
|
}
|
|
701
723
|
});
|
|
702
724
|
}
|
|
725
|
+
/**
|
|
726
|
+
* The network an idle session changed on its phone (profile, interceptor
|
|
727
|
+
* proxy), put back before the idle release frees the phone. Imported lazily,
|
|
728
|
+
* as this module's other session services are. Never throws.
|
|
729
|
+
*/
|
|
730
|
+
function restoreIdleSessionNetwork(sessionId) {
|
|
731
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
732
|
+
if (!sessionId)
|
|
733
|
+
return;
|
|
734
|
+
try {
|
|
735
|
+
const { PhoneNetworkRestore } = yield Promise.resolve().then(() => __importStar(require('./services/network/PhoneNetworkRestore')));
|
|
736
|
+
yield typedi_1.Container.get(PhoneNetworkRestore).restoreSession(sessionId, 'idle timeout');
|
|
737
|
+
}
|
|
738
|
+
catch (err) {
|
|
739
|
+
logger_1.default.warn(`Could not put back the network of idle session ${sessionId}: ${err}`);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* The idle session's virtual elements and autowait settings
|
|
745
|
+
* (sessionMemory.ts). Imported lazily, as above. Never throws.
|
|
746
|
+
*/
|
|
747
|
+
function forgetIdleSessionMemory(sessionId) {
|
|
748
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
749
|
+
if (!sessionId)
|
|
750
|
+
return;
|
|
751
|
+
try {
|
|
752
|
+
const { forgetSessionMemory } = yield Promise.resolve().then(() => __importStar(require('./sessions/sessionMemory')));
|
|
753
|
+
forgetSessionMemory(sessionId);
|
|
754
|
+
}
|
|
755
|
+
catch (err) {
|
|
756
|
+
logger_1.default.warn(`Could not forget idle session ${sessionId}'s elements: ${err}`);
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
}
|
|
703
760
|
function setupCronReleaseBlockedDevices(intervalMs, newCommandTimeoutSec) {
|
|
704
761
|
return __awaiter(this, void 0, void 0, function* () {
|
|
705
762
|
if (cronTimerToReleaseBlockedDevices) {
|
|
@@ -805,11 +862,15 @@ function setupCronCleanExpiredReservations(intervalMs) {
|
|
|
805
862
|
});
|
|
806
863
|
}
|
|
807
864
|
/**
|
|
808
|
-
* Sets up a cron job to purge older builds and sessions based on configuration
|
|
865
|
+
* Sets up a cron job to purge older builds and sessions based on configuration.
|
|
866
|
+
* The schedule is the one saved on the dashboard's Maintenance page, else the
|
|
867
|
+
* `buildCleanupSchedule` option. Calling it again replaces the job, which is
|
|
868
|
+
* how a schedule saved at runtime takes effect (`rescheduleCleanupBuilds`).
|
|
809
869
|
*/
|
|
810
870
|
function setupCronCleanupBuilds(pluginArgs) {
|
|
811
871
|
return __awaiter(this, void 0, void 0, function* () {
|
|
812
|
-
const {
|
|
872
|
+
const { loadEffectiveSettings } = yield Promise.resolve().then(() => __importStar(require('./services/settings/labSettings')));
|
|
873
|
+
const { buildCleanupSchedule } = yield loadEffectiveSettings(pluginArgs);
|
|
813
874
|
const { CleanupService } = yield Promise.resolve().then(() => __importStar(require('./services/CleanupService')));
|
|
814
875
|
const schedule = yield Promise.resolve().then(() => __importStar(require('node-schedule')));
|
|
815
876
|
const cleanupService = typedi_1.Container.get(CleanupService);
|
|
@@ -823,6 +884,19 @@ function setupCronCleanupBuilds(pluginArgs) {
|
|
|
823
884
|
}));
|
|
824
885
|
});
|
|
825
886
|
}
|
|
887
|
+
/**
|
|
888
|
+
* Put a cleanup schedule just saved on the Maintenance page to work: the old
|
|
889
|
+
* timer is cancelled and a new one installed. A server that runs no cleanup
|
|
890
|
+
* (a cloud-provider hub, see `setupMaintenanceCrons`) stays without one.
|
|
891
|
+
*/
|
|
892
|
+
function rescheduleCleanupBuilds(pluginArgs) {
|
|
893
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
894
|
+
var _a;
|
|
895
|
+
if ((_a = pluginArgs.cloud) === null || _a === void 0 ? void 0 : _a.cloudName)
|
|
896
|
+
return;
|
|
897
|
+
yield setupCronCleanupBuilds(pluginArgs);
|
|
898
|
+
});
|
|
899
|
+
}
|
|
826
900
|
let cronTimerSweepOrphanSessions;
|
|
827
901
|
let cronTimerReconcileDevices;
|
|
828
902
|
let cronTimerSelectorVerification;
|
|
@@ -34,7 +34,7 @@ const createWith = (hubGrants) => ({
|
|
|
34
34
|
});
|
|
35
35
|
const dashboardHooks = {
|
|
36
36
|
before: (sessionId, command, req, res) => event_manager_1.DASHBORD_EVENT_MANAGER.beforeSessionCommand(sessionId, command, req, res),
|
|
37
|
-
after: (sessionId, command, req, res, body) => event_manager_1.DASHBORD_EVENT_MANAGER.afterSessionCommand(sessionId, command, null, req, res, body),
|
|
37
|
+
after: (sessionId, command, req, res, body, heal) => event_manager_1.DASHBORD_EVENT_MANAGER.afterSessionCommand(sessionId, command, null, req, res, body, heal),
|
|
38
38
|
};
|
|
39
39
|
/** A hub: sessions other servers run are forwarded to them. */
|
|
40
40
|
function hubGatewayOptions(config) {
|
|
@@ -15,6 +15,7 @@ const https_proxy_agent_1 = require("https-proxy-agent");
|
|
|
15
15
|
const http_proxy_agent_1 = require("http-proxy-agent");
|
|
16
16
|
const internalCall_1 = require("./internalCall");
|
|
17
17
|
const hubSessionToken_1 = require("./hubSessionToken");
|
|
18
|
+
const healReport_1 = require("./healReport");
|
|
18
19
|
/**
|
|
19
20
|
* Sending a session command on to the node that runs the session, and handing
|
|
20
21
|
* the node's answer back to the client unchanged.
|
|
@@ -44,6 +45,7 @@ const NEVER_FORWARDED = new Set([
|
|
|
44
45
|
'x-xenon-token',
|
|
45
46
|
internalCall_1.INTERNAL_CALL_HEADER,
|
|
46
47
|
hubSessionToken_1.HUB_TOKEN_HEADER,
|
|
48
|
+
healReport_1.HEAL_REPORT_HEADER,
|
|
47
49
|
]);
|
|
48
50
|
function forwardedRequestHeaders(incoming) {
|
|
49
51
|
const out = {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HEAL_REPORT_HEADER = void 0;
|
|
4
|
+
exports.runReportingHeals = runReportingHeals;
|
|
5
|
+
exports.reportHeal = reportHeal;
|
|
6
|
+
exports.takeHealReport = takeHealReport;
|
|
7
|
+
const async_hooks_1 = require("async_hooks");
|
|
8
|
+
/**
|
|
9
|
+
* A heal on a node's phone, on its way back to the hub.
|
|
10
|
+
*
|
|
11
|
+
* A session the hub created runs its commands on the node, so the node's
|
|
12
|
+
* CommandInterceptor heals its finds. The node keeps no record of such a
|
|
13
|
+
* session; the hub does. So the node puts the heal on its answer to the
|
|
14
|
+
* forwarded command, as the `x-xenon-heal` header, and the hub takes it off
|
|
15
|
+
* before relaying the answer and records it with the command, as it records a
|
|
16
|
+
* local session's heal.
|
|
17
|
+
*
|
|
18
|
+
* The node's session gateway runs a command that came from a hub inside
|
|
19
|
+
* `runReportingHeals`, so the interceptor, deep inside Appium's route, finds
|
|
20
|
+
* the answer to put it on. Anywhere else `reportHeal` does nothing, and the
|
|
21
|
+
* server records the heal itself.
|
|
22
|
+
*/
|
|
23
|
+
exports.HEAL_REPORT_HEADER = 'x-xenon-heal';
|
|
24
|
+
/**
|
|
25
|
+
* A header longer than this isn't sent: the hub's HTTP parser refuses an
|
|
26
|
+
* answer whose headers pass 16 KB, and a refused answer would fail the
|
|
27
|
+
* command. A heal of selectors that long goes unrecorded instead.
|
|
28
|
+
*/
|
|
29
|
+
const MAX_HEADER_LENGTH = 8 * 1024;
|
|
30
|
+
const answers = new async_hooks_1.AsyncLocalStorage();
|
|
31
|
+
/** Run the rest of a command that came from a hub with its answer at hand. */
|
|
32
|
+
function runReportingHeals(res, fn) {
|
|
33
|
+
const answer = { res };
|
|
34
|
+
const forget = () => {
|
|
35
|
+
answer.res = null;
|
|
36
|
+
};
|
|
37
|
+
res.once('finish', forget);
|
|
38
|
+
res.once('close', forget);
|
|
39
|
+
return answers.run(answer, fn);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Put `heal` on the answer to the hub's command this code runs for.
|
|
43
|
+
* `not-from-hub` when the command didn't come from a hub (or the answer has
|
|
44
|
+
* gone): the caller records the heal itself.
|
|
45
|
+
*/
|
|
46
|
+
function reportHeal(heal) {
|
|
47
|
+
var _a;
|
|
48
|
+
const res = (_a = answers.getStore()) === null || _a === void 0 ? void 0 : _a.res;
|
|
49
|
+
if (!res || res.headersSent)
|
|
50
|
+
return 'not-from-hub';
|
|
51
|
+
const encoded = Buffer.from(JSON.stringify(heal), 'utf8').toString('base64url');
|
|
52
|
+
if (encoded.length > MAX_HEADER_LENGTH)
|
|
53
|
+
return 'too-long';
|
|
54
|
+
res.setHeader(exports.HEAL_REPORT_HEADER, encoded);
|
|
55
|
+
return 'reported';
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The heal a node put on its answer, taken off the headers so it isn't
|
|
59
|
+
* relayed to the client. Undefined when there is none, or it isn't one.
|
|
60
|
+
*/
|
|
61
|
+
function takeHealReport(headers) {
|
|
62
|
+
const raw = headers[exports.HEAL_REPORT_HEADER];
|
|
63
|
+
delete headers[exports.HEAL_REPORT_HEADER];
|
|
64
|
+
if (typeof raw !== 'string' || !raw)
|
|
65
|
+
return undefined;
|
|
66
|
+
try {
|
|
67
|
+
const heal = JSON.parse(Buffer.from(raw, 'base64url').toString('utf8'));
|
|
68
|
+
if (!heal ||
|
|
69
|
+
typeof heal.originalSelector !== 'string' ||
|
|
70
|
+
typeof heal.healedSelector !== 'string' ||
|
|
71
|
+
typeof heal.confidence !== 'number') {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const optional = (v) => (typeof v === 'string' ? v : undefined);
|
|
75
|
+
return {
|
|
76
|
+
originalSelector: heal.originalSelector,
|
|
77
|
+
originalStrategy: optional(heal.originalStrategy),
|
|
78
|
+
healedSelector: heal.healedSelector,
|
|
79
|
+
healedStrategy: optional(heal.healedStrategy),
|
|
80
|
+
confidence: heal.confidence,
|
|
81
|
+
tier: typeof heal.tier === 'number' ? heal.tier : undefined,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (_a) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -15,6 +15,7 @@ exports.createHubRouting = createHubRouting;
|
|
|
15
15
|
const commandAuth_1 = require("../middleware/commandAuth");
|
|
16
16
|
const internalCall_1 = require("./internalCall");
|
|
17
17
|
const hubSessionToken_1 = require("./hubSessionToken");
|
|
18
|
+
const healReport_1 = require("./healReport");
|
|
18
19
|
const forwardToNode_1 = require("./forwardToNode");
|
|
19
20
|
/** Where a session runs could not be determined (a database error). */
|
|
20
21
|
exports.ROUTING_UNAVAILABLE_STATUS = 503;
|
|
@@ -62,7 +63,14 @@ function createSessionGatewayLayer(deps) {
|
|
|
62
63
|
const latency = (_b = (_a = deps.latencyOf) === null || _a === void 0 ? void 0 : _a.call(deps, sessionId)) !== null && _b !== void 0 ? _b : 0;
|
|
63
64
|
if (latency > 0)
|
|
64
65
|
yield new Promise((resolve) => setTimeout(resolve, latency));
|
|
65
|
-
|
|
66
|
+
// On a node, a heal of the hub's command goes back to the hub on the answer
|
|
67
|
+
// (healReport.ts). The header proves nothing elsewhere: a hub or a
|
|
68
|
+
// standalone server checks no hub token, so a client could send one there
|
|
69
|
+
// to keep its heals out of the record.
|
|
70
|
+
if (fromHub && deps.hubTokens)
|
|
71
|
+
(0, healReport_1.runReportingHeals)(res, next);
|
|
72
|
+
else
|
|
73
|
+
next();
|
|
66
74
|
});
|
|
67
75
|
}
|
|
68
76
|
return function xenonSessionGateway(req, res, next) {
|
|
@@ -176,8 +184,10 @@ function createHubRouting(deps) {
|
|
|
176
184
|
abort.abort();
|
|
177
185
|
});
|
|
178
186
|
let captured;
|
|
187
|
+
let heal;
|
|
179
188
|
try {
|
|
180
189
|
const upstream = yield (0, forwardToNode_1.sendToNode)(yield nodeRequest(req, sessionId, location, abort.signal));
|
|
190
|
+
heal = (0, healReport_1.takeHealReport)(upstream.headers);
|
|
181
191
|
captured = yield (0, forwardToNode_1.relayAnswer)(upstream, res, !!hooks);
|
|
182
192
|
}
|
|
183
193
|
catch (error) {
|
|
@@ -192,7 +202,7 @@ function createHubRouting(deps) {
|
|
|
192
202
|
}
|
|
193
203
|
if (hooks && captured !== undefined) {
|
|
194
204
|
yield hooks
|
|
195
|
-
.after(sessionId, command, req, res, captured)
|
|
205
|
+
.after(sessionId, command, req, res, captured, heal)
|
|
196
206
|
.catch((error) => logger.warn(`Dashboard log failed for session ${sessionId}: ${(0, commandAuth_1.summarize)(error)}`));
|
|
197
207
|
}
|
|
198
208
|
});
|