@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
|
@@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
+
var t = {};
|
|
13
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
+
t[p] = s[p];
|
|
15
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
+
t[p[i]] = s[p[i]];
|
|
19
|
+
}
|
|
20
|
+
return t;
|
|
21
|
+
};
|
|
11
22
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
23
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
24
|
};
|
|
@@ -15,6 +26,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
26
|
exports.DeviceStoreFactory = void 0;
|
|
16
27
|
const deviceFieldOwners_1 = require("./deviceFieldOwners");
|
|
17
28
|
const deviceClaims_1 = require("./deviceClaims");
|
|
29
|
+
const appleFamily_1 = require("./appleFamily");
|
|
30
|
+
const deviceSettings_1 = require("./deviceSettings");
|
|
31
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
18
32
|
const logger_1 = __importDefault(require("../logger"));
|
|
19
33
|
const semver_1 = __importDefault(require("semver"));
|
|
20
34
|
const db_1 = require("./db");
|
|
@@ -63,6 +77,10 @@ class LokiDeviceStore {
|
|
|
63
77
|
if (!nameRegex.test(device.name || ''))
|
|
64
78
|
return false;
|
|
65
79
|
}
|
|
80
|
+
// iPhone / iPad Filter (see appleFamilyOf)
|
|
81
|
+
if (filterOptions.appleFamily && (0, appleFamily_1.appleFamilyOf)(device) !== filterOptions.appleFamily) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
66
84
|
// UDID Filter
|
|
67
85
|
if (filterOptions.udid) {
|
|
68
86
|
if (Array.isArray(filterOptions.udid)) {
|
|
@@ -147,6 +165,16 @@ class LokiDeviceStore {
|
|
|
147
165
|
}
|
|
148
166
|
updateDevice(udid, host, updateData) {
|
|
149
167
|
return __awaiter(this, void 0, void 0, function* () {
|
|
168
|
+
// Saved for the phone first, as PrismaDeviceStore.updateDevice does.
|
|
169
|
+
const settings = lodash_1.default.cloneDeep(lodash_1.default.omitBy((0, deviceFieldOwners_1.pickSettingFields)(updateData), (v) => v === undefined));
|
|
170
|
+
if (Object.keys(settings).length > 0) {
|
|
171
|
+
const model = yield db_1.XenonDatabase.DeviceSettingsModel;
|
|
172
|
+
const saved = model.findOne({ udid, host });
|
|
173
|
+
if (saved)
|
|
174
|
+
model.update(Object.assign(saved, settings));
|
|
175
|
+
else
|
|
176
|
+
model.insert(Object.assign({ udid, host, userBlocked: false }, settings));
|
|
177
|
+
}
|
|
150
178
|
(yield db_1.XenonDatabase.DeviceModel)
|
|
151
179
|
.chain()
|
|
152
180
|
.find({ udid, host })
|
|
@@ -167,7 +195,10 @@ class LokiDeviceStore {
|
|
|
167
195
|
}
|
|
168
196
|
addDevices(devices_1) {
|
|
169
197
|
return __awaiter(this, arguments, void 0, function* (devices, options = {}) {
|
|
198
|
+
var _a;
|
|
170
199
|
const deviceModel = yield db_1.XenonDatabase.DeviceModel;
|
|
200
|
+
const settingsModel = yield db_1.XenonDatabase.DeviceSettingsModel;
|
|
201
|
+
const now = Date.now();
|
|
171
202
|
const added = [];
|
|
172
203
|
for (const device of devices) {
|
|
173
204
|
const existing = deviceModel.findOne({ udid: device.udid, host: device.host });
|
|
@@ -184,10 +215,13 @@ class LokiDeviceStore {
|
|
|
184
215
|
continue;
|
|
185
216
|
}
|
|
186
217
|
if (!existing) {
|
|
218
|
+
// The phone's saved settings, as PrismaDeviceStore.addDevices.
|
|
219
|
+
const saved = settingsModel.findOne({ udid: device.udid, host: (_a = device.host) !== null && _a !== void 0 ? _a : 'Local' });
|
|
220
|
+
const start = saved ? (0, deviceSettings_1.restoredColumns)(saved, now) : {};
|
|
187
221
|
const cleanDevice = options.nodeReport
|
|
188
|
-
? Object.assign(Object.assign({}, (0, deviceFieldOwners_1.pickNodeReportFields)(device)), { udid: device.udid, host: device.host, busy: nodeBusy, nodeBusy,
|
|
222
|
+
? Object.assign(Object.assign(Object.assign({}, (0, deviceFieldOwners_1.pickNodeReportFields)(device)), start), { udid: device.udid, host: device.host, busy: nodeBusy, nodeBusy,
|
|
189
223
|
nodeHold })
|
|
190
|
-
: Object.assign({}, device);
|
|
224
|
+
: Object.assign(Object.assign({}, device), start);
|
|
191
225
|
if (cleanDevice.host === undefined)
|
|
192
226
|
cleanDevice.host = 'Local';
|
|
193
227
|
if (cleanDevice.userBlocked === undefined)
|
|
@@ -227,6 +261,43 @@ class LokiDeviceStore {
|
|
|
227
261
|
}
|
|
228
262
|
});
|
|
229
263
|
}
|
|
264
|
+
adoptSettings() {
|
|
265
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
const settingsModel = yield db_1.XenonDatabase.DeviceSettingsModel;
|
|
268
|
+
let adopted = 0;
|
|
269
|
+
for (const device of (yield db_1.XenonDatabase.DeviceModel).find()) {
|
|
270
|
+
if (!(0, deviceSettings_1.hasSettings)(device, now))
|
|
271
|
+
continue;
|
|
272
|
+
if (settingsModel.findOne({ udid: device.udid, host: device.host }))
|
|
273
|
+
continue;
|
|
274
|
+
settingsModel.insert(Object.assign(Object.assign({}, (0, deviceFieldOwners_1.pickSettingFields)(device)), { userBlocked: device.userBlocked === true, udid: device.udid, host: device.host }));
|
|
275
|
+
adopted++;
|
|
276
|
+
}
|
|
277
|
+
return adopted;
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
forgetSettings(onlyHosts) {
|
|
281
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
282
|
+
const model = yield db_1.XenonDatabase.DeviceSettingsModel;
|
|
283
|
+
if (onlyHosts) {
|
|
284
|
+
model
|
|
285
|
+
.chain()
|
|
286
|
+
.find({ host: { $in: [...onlyHosts] } })
|
|
287
|
+
.remove();
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
model.removeDataOnly();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
findSavedPhones(udid) {
|
|
295
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
296
|
+
return (yield db_1.XenonDatabase.DeviceSettingsModel)
|
|
297
|
+
.find({ udid })
|
|
298
|
+
.map((saved) => ({ udid: saved.udid, host: saved.host }));
|
|
299
|
+
});
|
|
300
|
+
}
|
|
230
301
|
findDevice(filter) {
|
|
231
302
|
return __awaiter(this, void 0, void 0, function* () {
|
|
232
303
|
return (yield db_1.XenonDatabase.DeviceModel).findOne(filter);
|
|
@@ -378,7 +449,9 @@ class LokiHealEtalonStore {
|
|
|
378
449
|
const coll = yield this.getCollection();
|
|
379
450
|
const existing = coll.findOne({ selector: etalon.selector });
|
|
380
451
|
if (existing) {
|
|
381
|
-
|
|
452
|
+
// A fingerprint saved without a path keeps the one stored, as in Prisma.
|
|
453
|
+
const { path } = etalon, rest = __rest(etalon, ["path"]);
|
|
454
|
+
Object.assign(existing, rest, path == null ? {} : { path });
|
|
382
455
|
coll.update(existing);
|
|
383
456
|
}
|
|
384
457
|
else {
|
|
@@ -9,6 +9,8 @@ exports.hasNoClaim = hasNoClaim;
|
|
|
9
9
|
exports.isUnheld = isUnheld;
|
|
10
10
|
exports.nodeHoldOf = nodeHoldOf;
|
|
11
11
|
exports.heldHere = heldHere;
|
|
12
|
+
exports.countsTowardMaxSessions = countsTowardMaxSessions;
|
|
13
|
+
exports.sessionCap = sessionCap;
|
|
12
14
|
const manualLock_1 = require("../services/recording/manualLock");
|
|
13
15
|
/**
|
|
14
16
|
* Why a phone is busy, on the server that allocates it.
|
|
@@ -126,5 +128,40 @@ function heldHere(device) {
|
|
|
126
128
|
return true;
|
|
127
129
|
return device.session_id != null || device.claimedAt != null || device.claimSessionId != null;
|
|
128
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Whether a busy phone uses one of `maxSessions`' slots: an Appium session
|
|
133
|
+
* runs on it, or is being created on it.
|
|
134
|
+
*
|
|
135
|
+
* - **Counts:** this server's claim, pending (the session is being created) or
|
|
136
|
+
* with its session id; an Appium session's id in `session_id`; on a hub, a
|
|
137
|
+
* node's phone the node reports busy, unless the node says that is a preview.
|
|
138
|
+
* - **Does not count:** a live preview or a recording (`manual_...` in
|
|
139
|
+
* `session_id`), and a phone an SDK lease holds that has no session on it
|
|
140
|
+
* (`busy` alone, see above). Neither is an Appium session, and a person
|
|
141
|
+
* watching three phones must not stop tests running on a fourth. A session
|
|
142
|
+
* started on a leased phone claims it, so it counts like any other.
|
|
143
|
+
*
|
|
144
|
+
* `busy` alone is not enough: until this existed the cap counted every busy
|
|
145
|
+
* phone, and compared with `===`.
|
|
146
|
+
*/
|
|
147
|
+
function countsTowardMaxSessions(device) {
|
|
148
|
+
if (!device.busy)
|
|
149
|
+
return false;
|
|
150
|
+
if (device.claimedAt != null || device.claimSessionId != null)
|
|
151
|
+
return true;
|
|
152
|
+
if (device.session_id != null)
|
|
153
|
+
return !(0, manualLock_1.isManualLock)(device.session_id);
|
|
154
|
+
return device.nodeBusy === true && !device.nodeHold;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The `maxSessions` setting as a limit: undefined (no limit) unless it is a
|
|
158
|
+
* positive number. 0 or less would hold every session back for ever, which
|
|
159
|
+
* nobody sets on purpose.
|
|
160
|
+
*/
|
|
161
|
+
function sessionCap(maxSessions) {
|
|
162
|
+
return typeof maxSessions === 'number' && Number.isFinite(maxSessions) && maxSessions >= 1
|
|
163
|
+
? maxSessions
|
|
164
|
+
: undefined;
|
|
165
|
+
}
|
|
129
166
|
/** What ending a claim resets: the claim, and the session's bookkeeping. */
|
|
130
167
|
exports.CLAIM_RESET = Object.assign(Object.assign({}, exports.NO_CLAIM), { session_id: null, lastCmdExecutedAt: null, sessionStartTime: 0, newCommandTimeout: null, sessionProgress: '', owningSessionId: null, lockedAt: null });
|
|
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.NODE_REPORT_FIELDS = exports.DISCOVERY_FIELDS = void 0;
|
|
6
|
+
exports.NODE_REPORT_FIELDS = exports.DISCOVERY_FIELDS = exports.SETTING_FIELDS = void 0;
|
|
7
|
+
exports.pickSettingFields = pickSettingFields;
|
|
7
8
|
exports.pickDiscoveryFields = pickDiscoveryFields;
|
|
8
9
|
exports.pickNodeReportFields = pickNodeReportFields;
|
|
9
10
|
exports.discoveryChanges = discoveryChanges;
|
|
@@ -23,11 +24,15 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
23
24
|
* (`POST /xenon/api/register`, NODE_REPORT_FIELDS). The report carries what
|
|
24
25
|
* the node observes of its own phone: `discovery`, and `observed` (its health,
|
|
25
26
|
* which only the server driving the phone can check). It never writes a
|
|
26
|
-
* `runtime` column
|
|
27
|
-
*
|
|
28
|
-
* the phone is busy goes to `nodeBusy` (`report`), never to `busy`
|
|
27
|
+
* `runtime` column (the hub's claim for a session) or a `setting`. Whether the
|
|
28
|
+
* node says the phone is busy goes to `nodeBusy` (`report`), never to `busy`
|
|
29
29
|
* (deviceClaims.ts).
|
|
30
30
|
*
|
|
31
|
+
* A `setting` is what people give the phone: an admin its team, tags and
|
|
32
|
+
* maintenance, a member a reservation. Those are also saved apart from the
|
|
33
|
+
* row (DeviceSetting, deviceSettings.ts), which is deleted whenever the phone
|
|
34
|
+
* goes, and a new row for the phone starts from them.
|
|
35
|
+
*
|
|
31
36
|
* Typed over every column, so a new column won't compile until it is placed.
|
|
32
37
|
*/
|
|
33
38
|
const OWNER = {
|
|
@@ -71,13 +76,6 @@ const OWNER = {
|
|
|
71
76
|
lockedAt: 'runtime',
|
|
72
77
|
claimSessionId: 'runtime',
|
|
73
78
|
claimedAt: 'runtime',
|
|
74
|
-
userBlocked: 'runtime',
|
|
75
|
-
reservationReason: 'runtime',
|
|
76
|
-
reservedBy: 'runtime',
|
|
77
|
-
reservedByUserId: 'runtime',
|
|
78
|
-
reservedUntil: 'runtime',
|
|
79
|
-
teamId: 'runtime',
|
|
80
|
-
tags: 'runtime',
|
|
81
79
|
totalUtilizationTimeMilliSec: 'runtime',
|
|
82
80
|
total_session_count: 'runtime',
|
|
83
81
|
dashboard_link: 'runtime',
|
|
@@ -91,7 +89,23 @@ const OWNER = {
|
|
|
91
89
|
storageFree: 'observed',
|
|
92
90
|
nodeBusy: 'report',
|
|
93
91
|
nodeHold: 'report',
|
|
92
|
+
teamId: 'setting',
|
|
93
|
+
tags: 'setting',
|
|
94
|
+
userBlocked: 'setting',
|
|
95
|
+
reservationReason: 'setting',
|
|
96
|
+
reservedBy: 'setting',
|
|
97
|
+
reservedByUserId: 'setting',
|
|
98
|
+
reservedUntil: 'setting',
|
|
94
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* The settings people give a phone, saved apart from its row (DeviceSetting
|
|
102
|
+
* has exactly these columns, besides its key).
|
|
103
|
+
*/
|
|
104
|
+
exports.SETTING_FIELDS = Object.keys(OWNER).filter((column) => OWNER[column] === 'setting');
|
|
105
|
+
/** Only the setting columns of `data`. */
|
|
106
|
+
function pickSettingFields(data) {
|
|
107
|
+
return lodash_1.default.pick(data, exports.SETTING_FIELDS);
|
|
108
|
+
}
|
|
95
109
|
/** The columns a sync may write to a phone it already knows. */
|
|
96
110
|
exports.DISCOVERY_FIELDS = new Set(Object.keys(OWNER).filter((column) => OWNER[column] === 'discovery'));
|
|
97
111
|
/** Only the discovery columns of `data`. */
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.restoredColumns = restoredColumns;
|
|
16
|
+
exports.settingsDiffer = settingsDiffer;
|
|
17
|
+
exports.hasSettings = hasSettings;
|
|
18
|
+
exports.resetDevicesAtBoot = resetDevicesAtBoot;
|
|
19
|
+
const localDeviceHosts_1 = require("../device-managers/localDeviceHosts");
|
|
20
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
21
|
+
const deviceFieldOwners_1 = require("./deviceFieldOwners");
|
|
22
|
+
/**
|
|
23
|
+
* The setting columns a new row for the phone starts with: all of them, so
|
|
24
|
+
* what was cleared stays cleared, and a reservation only while it holds.
|
|
25
|
+
*/
|
|
26
|
+
function restoredColumns(saved, now) {
|
|
27
|
+
var _a, _b, _c, _d, _e;
|
|
28
|
+
const holds = typeof saved.reservedUntil === 'number' && saved.reservedUntil > now;
|
|
29
|
+
return {
|
|
30
|
+
teamId: (_a = saved.teamId) !== null && _a !== void 0 ? _a : null,
|
|
31
|
+
// A copy: the in-memory store keeps the array itself.
|
|
32
|
+
tags: Array.isArray(saved.tags) ? [...saved.tags] : ((_b = saved.tags) !== null && _b !== void 0 ? _b : null),
|
|
33
|
+
userBlocked: saved.userBlocked === true,
|
|
34
|
+
reservationReason: holds ? ((_c = saved.reservationReason) !== null && _c !== void 0 ? _c : null) : null,
|
|
35
|
+
reservedBy: holds ? ((_d = saved.reservedBy) !== null && _d !== void 0 ? _d : null) : null,
|
|
36
|
+
reservedByUserId: holds ? ((_e = saved.reservedByUserId) !== null && _e !== void 0 ? _e : null) : null,
|
|
37
|
+
reservedUntil: holds ? saved.reservedUntil : null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Whether two sets of setting columns differ (restoredColumns' shape). */
|
|
41
|
+
function settingsDiffer(a, b) {
|
|
42
|
+
return deviceFieldOwners_1.SETTING_FIELDS.some((field) => JSON.stringify(a[field]) !== JSON.stringify(b[field]));
|
|
43
|
+
}
|
|
44
|
+
function hasTags(tags) {
|
|
45
|
+
if (tags == null)
|
|
46
|
+
return false;
|
|
47
|
+
if (Array.isArray(tags))
|
|
48
|
+
return tags.length > 0;
|
|
49
|
+
return tags !== '' && tags !== '[]';
|
|
50
|
+
}
|
|
51
|
+
/** Whether a row carries a setting that is worth keeping (adoptSettings). */
|
|
52
|
+
function hasSettings(row, now) {
|
|
53
|
+
return (row.teamId != null ||
|
|
54
|
+
row.userBlocked === true ||
|
|
55
|
+
hasTags(row.tags) ||
|
|
56
|
+
(typeof row.reservedUntil === 'number' && row.reservedUntil > now));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The device table as a server starts. It forgets the phones
|
|
60
|
+
* devicesClearedAtBoot names, but first saves the settings of rows that have
|
|
61
|
+
* none saved (rows written through 2.13 kept them only on the row). With
|
|
62
|
+
* `removeDevicesFromDatabaseBeforeRunningThePlugin`, the saved settings of
|
|
63
|
+
* those phones are forgotten with them.
|
|
64
|
+
*/
|
|
65
|
+
function resetDevicesAtBoot(store, args, local) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
const cleared = (0, localDeviceHosts_1.devicesClearedAtBoot)(args, local);
|
|
68
|
+
const adopted = yield store.adoptSettings();
|
|
69
|
+
if (adopted > 0) {
|
|
70
|
+
logger_1.default.info(`Saved the team, tags, maintenance or reservation of ${adopted} phone(s)`);
|
|
71
|
+
}
|
|
72
|
+
if (args.removeDevicesFromDatabaseBeforeRunningThePlugin) {
|
|
73
|
+
yield store.forgetSettings(cleared);
|
|
74
|
+
logger_1.default.info('removeDevicesFromDatabaseBeforeRunningThePlugin is on: forgot the saved settings of ' +
|
|
75
|
+
(cleared ? "this server's own phones" : 'every phone'));
|
|
76
|
+
}
|
|
77
|
+
yield store.clearStorage(cleared);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -41,6 +41,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
41
41
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
45
|
+
var t = {};
|
|
46
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
47
|
+
t[p] = s[p];
|
|
48
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
49
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
50
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
51
|
+
t[p[i]] = s[p[i]];
|
|
52
|
+
}
|
|
53
|
+
return t;
|
|
54
|
+
};
|
|
44
55
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
56
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
57
|
};
|
|
@@ -53,8 +64,13 @@ const logger_1 = __importDefault(require("../logger"));
|
|
|
53
64
|
const deviceColumns_1 = require("./deviceColumns");
|
|
54
65
|
const deviceFieldOwners_1 = require("./deviceFieldOwners");
|
|
55
66
|
const deviceClaims_1 = require("./deviceClaims");
|
|
67
|
+
const appleFamily_1 = require("./appleFamily");
|
|
68
|
+
const deviceSettings_1 = require("./deviceSettings");
|
|
56
69
|
/** Logged once per key, so a chatty node doesn't flood the log. */
|
|
57
70
|
const droppedDeviceKeys = new Set();
|
|
71
|
+
const keyOf = (d) => `${d.udid}@${d.host}`;
|
|
72
|
+
/** Prisma's `select` for a phone's key and its setting columns. */
|
|
73
|
+
const SETTINGS_SELECT = Object.fromEntries(['udid', 'host', ...deviceFieldOwners_1.SETTING_FIELDS].map((field) => [field, true]));
|
|
58
74
|
class PrismaDeviceStore {
|
|
59
75
|
get prisma() {
|
|
60
76
|
return typedi_1.Container.get(prisma_service_1.PrismaService).client;
|
|
@@ -104,7 +120,7 @@ class PrismaDeviceStore {
|
|
|
104
120
|
}
|
|
105
121
|
getDevices(filterOptions) {
|
|
106
122
|
return __awaiter(this, void 0, void 0, function* () {
|
|
107
|
-
var _a;
|
|
123
|
+
var _a, _b;
|
|
108
124
|
const where = {};
|
|
109
125
|
if (filterOptions.platform) {
|
|
110
126
|
where.platform = filterOptions.platform;
|
|
@@ -191,6 +207,22 @@ class PrismaDeviceStore {
|
|
|
191
207
|
return filterOptions.tags.every((tag) => d.tags.includes(tag));
|
|
192
208
|
});
|
|
193
209
|
}
|
|
210
|
+
// `appium:iPhoneOnly` / `appium:iPadOnly`. Read from the phone's model and
|
|
211
|
+
// form factor first, not its name: see appleFamilyOf.
|
|
212
|
+
if (filterOptions.appleFamily) {
|
|
213
|
+
results = results.filter((d) => (0, appleFamily_1.appleFamilyOf)(d) === filterOptions.appleFamily);
|
|
214
|
+
}
|
|
215
|
+
// A name filter, as the in-memory store has always applied it: a part of
|
|
216
|
+
// the phone's name. Case does not matter, and a phone's marketing name
|
|
217
|
+
// ("iPhone 16 Pro") counts as much as the name its owner gave it.
|
|
218
|
+
const wanted = (_b = filterOptions.name) === null || _b === void 0 ? void 0 : _b.trim().toLowerCase();
|
|
219
|
+
if (wanted) {
|
|
220
|
+
results = results.filter((d) => {
|
|
221
|
+
var _a, _b;
|
|
222
|
+
return ((_a = d.name) !== null && _a !== void 0 ? _a : '').toLowerCase().includes(wanted) ||
|
|
223
|
+
((_b = d.marketingName) !== null && _b !== void 0 ? _b : '').toLowerCase().includes(wanted);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
194
226
|
return results;
|
|
195
227
|
});
|
|
196
228
|
}
|
|
@@ -202,6 +234,16 @@ class PrismaDeviceStore {
|
|
|
202
234
|
updateDevice(udid, host, update) {
|
|
203
235
|
return __awaiter(this, void 0, void 0, function* () {
|
|
204
236
|
const data = this.fromIDevice(update);
|
|
237
|
+
// Saved first: a row created meanwhile reads them after this, or is
|
|
238
|
+
// corrected by addDevices' second look (settleRestored).
|
|
239
|
+
const settings = (0, deviceFieldOwners_1.pickSettingFields)(data);
|
|
240
|
+
if (Object.keys(settings).length > 0) {
|
|
241
|
+
yield this.prisma.deviceSetting.upsert({
|
|
242
|
+
where: { udid_host: { udid, host } },
|
|
243
|
+
create: Object.assign({ udid, host }, settings),
|
|
244
|
+
update: settings,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
205
247
|
const result = yield this.prisma.device.updateMany({
|
|
206
248
|
where: { udid, host },
|
|
207
249
|
data,
|
|
@@ -209,6 +251,21 @@ class PrismaDeviceStore {
|
|
|
209
251
|
logger_1.default.debug(`[PrismaStore] Update device ${udid} at ${host}: ${result.count} records affected`);
|
|
210
252
|
});
|
|
211
253
|
}
|
|
254
|
+
/** The settings saved for these phones, by `udid@host`. */
|
|
255
|
+
savedSettings(phones) {
|
|
256
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
257
|
+
if (phones.length === 0)
|
|
258
|
+
return new Map();
|
|
259
|
+
const rows = yield this.prisma.deviceSetting.findMany({
|
|
260
|
+
where: { OR: phones.map(({ udid, host }) => ({ udid, host })) },
|
|
261
|
+
select: SETTINGS_SELECT,
|
|
262
|
+
});
|
|
263
|
+
return new Map(rows.map((r) => [
|
|
264
|
+
keyOf(r),
|
|
265
|
+
r,
|
|
266
|
+
]));
|
|
267
|
+
});
|
|
268
|
+
}
|
|
212
269
|
touchSession(sessionId, at) {
|
|
213
270
|
return __awaiter(this, void 0, void 0, function* () {
|
|
214
271
|
yield this.prisma.device.updateMany({
|
|
@@ -225,22 +282,58 @@ class PrismaDeviceStore {
|
|
|
225
282
|
where: { OR: devices.map((d) => ({ udid: d.udid, host: d.host })) },
|
|
226
283
|
select: { udid: true, host: true },
|
|
227
284
|
});
|
|
228
|
-
const known = new Set(existing.map((d) =>
|
|
285
|
+
const known = new Set(existing.map((d) => keyOf(d)));
|
|
286
|
+
const fresh = devices.filter((d) => !known.has(keyOf(d)));
|
|
287
|
+
// A new row starts from the phone's saved settings, in the write that
|
|
288
|
+
// creates it: it is never listed, even for a moment, outside its team.
|
|
289
|
+
const saved = yield this.savedSettings(fresh);
|
|
290
|
+
const now = Date.now();
|
|
291
|
+
const started = new Map();
|
|
229
292
|
const added = [];
|
|
230
293
|
for (const device of devices) {
|
|
294
|
+
const key = keyOf(device);
|
|
295
|
+
const was = known.has(key) ? undefined : saved.get(key);
|
|
296
|
+
const start = was ? (0, deviceSettings_1.restoredColumns)(was, now) : {};
|
|
297
|
+
if (was)
|
|
298
|
+
started.set(key, start);
|
|
231
299
|
const d = options.nodeReport
|
|
232
|
-
? yield this.addNodeReport(device)
|
|
300
|
+
? yield this.addNodeReport(device, start)
|
|
233
301
|
: yield this.prisma.device.upsert({
|
|
234
302
|
// Use upsert to avoid race conditions and unique constraint errors. A
|
|
235
303
|
// phone already here gets only its discovery columns (AddDevicesOptions).
|
|
236
304
|
where: { udid_host: { udid: device.udid, host: device.host } },
|
|
237
305
|
update: (0, deviceFieldOwners_1.pickDiscoveryFields)(this.fromIDevice(device)),
|
|
238
|
-
create: this.fromIDevice(device),
|
|
306
|
+
create: Object.assign(Object.assign({}, this.fromIDevice(device)), start),
|
|
239
307
|
});
|
|
240
|
-
if (!known.has(
|
|
308
|
+
if (!known.has(key))
|
|
241
309
|
added.push(this.toIDevice(d));
|
|
242
310
|
}
|
|
243
|
-
return added;
|
|
311
|
+
return this.settleRestored(added, started, now);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* A second look at the settings of the rows just created. A setting saved
|
|
316
|
+
* between the read above and the create (updateDevice saves before it
|
|
317
|
+
* writes the row, and found no row to write) would otherwise be missing
|
|
318
|
+
* from the new row until the phone came back again. Rows that need it are
|
|
319
|
+
* corrected and read again; the others are returned as they are.
|
|
320
|
+
*/
|
|
321
|
+
settleRestored(added, started, now) {
|
|
322
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
323
|
+
const saved = yield this.savedSettings(added);
|
|
324
|
+
return Promise.all(added.map((device) => __awaiter(this, void 0, void 0, function* () {
|
|
325
|
+
const latest = saved.get(keyOf(device));
|
|
326
|
+
if (!latest)
|
|
327
|
+
return device;
|
|
328
|
+
const columns = (0, deviceSettings_1.restoredColumns)(latest, now);
|
|
329
|
+
const before = started.get(keyOf(device));
|
|
330
|
+
if (before && !(0, deviceSettings_1.settingsDiffer)(before, columns))
|
|
331
|
+
return device;
|
|
332
|
+
const where = { udid: device.udid, host: device.host };
|
|
333
|
+
yield this.prisma.device.updateMany({ where, data: columns });
|
|
334
|
+
const row = yield this.prisma.device.findUnique({ where: { udid_host: where } });
|
|
335
|
+
return row ? this.toIDevice(row) : device;
|
|
336
|
+
})));
|
|
244
337
|
});
|
|
245
338
|
}
|
|
246
339
|
/**
|
|
@@ -250,7 +343,7 @@ class PrismaDeviceStore {
|
|
|
250
343
|
* update, so a report the node sent before the hub claimed the phone can't
|
|
251
344
|
* undo the claim.
|
|
252
345
|
*/
|
|
253
|
-
addNodeReport(device) {
|
|
346
|
+
addNodeReport(device, start) {
|
|
254
347
|
return __awaiter(this, void 0, void 0, function* () {
|
|
255
348
|
const { udid, host } = device;
|
|
256
349
|
const nodeBusy = device.busy === true;
|
|
@@ -258,7 +351,8 @@ class PrismaDeviceStore {
|
|
|
258
351
|
const row = yield this.prisma.device.upsert({
|
|
259
352
|
where: { udid_host: { udid, host } },
|
|
260
353
|
update: nodeBusy ? Object.assign(Object.assign({}, reported), { busy: true }) : reported,
|
|
261
|
-
|
|
354
|
+
// The hub's own settings for the phone, never the report's.
|
|
355
|
+
create: Object.assign(Object.assign(Object.assign({}, reported), start), { udid, host, busy: nodeBusy }),
|
|
262
356
|
});
|
|
263
357
|
if (nodeBusy)
|
|
264
358
|
return row;
|
|
@@ -299,6 +393,54 @@ class PrismaDeviceStore {
|
|
|
299
393
|
yield this.prisma.device.deleteMany(onlyHosts ? { where: { host: { in: [...onlyHosts] } } } : undefined);
|
|
300
394
|
});
|
|
301
395
|
}
|
|
396
|
+
adoptSettings() {
|
|
397
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
398
|
+
const now = Date.now();
|
|
399
|
+
const rows = (yield this.prisma.device.findMany({
|
|
400
|
+
where: {
|
|
401
|
+
OR: [
|
|
402
|
+
{ teamId: { not: null } },
|
|
403
|
+
{ userBlocked: true },
|
|
404
|
+
{ tags: { notIn: ['', '[]'] } },
|
|
405
|
+
{ reservedUntil: { gt: now } },
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
select: SETTINGS_SELECT,
|
|
409
|
+
}));
|
|
410
|
+
const candidates = rows.filter((row) => (0, deviceSettings_1.hasSettings)(row, now));
|
|
411
|
+
if (candidates.length === 0)
|
|
412
|
+
return 0;
|
|
413
|
+
const saved = yield this.savedSettings(candidates);
|
|
414
|
+
let adopted = 0;
|
|
415
|
+
for (let _a of candidates) {
|
|
416
|
+
const { udid, host } = _a, settings = __rest(_a, ["udid", "host"]);
|
|
417
|
+
if (saved.has(keyOf({ udid, host })))
|
|
418
|
+
continue;
|
|
419
|
+
const data = (0, deviceFieldOwners_1.pickSettingFields)(settings);
|
|
420
|
+
yield this.prisma.deviceSetting.upsert({
|
|
421
|
+
where: { udid_host: { udid, host } },
|
|
422
|
+
create: Object.assign(Object.assign({}, data), { userBlocked: settings.userBlocked === true, udid, host }),
|
|
423
|
+
// Saved meanwhile: that one is newer.
|
|
424
|
+
update: {},
|
|
425
|
+
});
|
|
426
|
+
adopted++;
|
|
427
|
+
}
|
|
428
|
+
return adopted;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
forgetSettings(onlyHosts) {
|
|
432
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
433
|
+
yield this.prisma.deviceSetting.deleteMany(onlyHosts ? { where: { host: { in: [...onlyHosts] } } } : undefined);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
findSavedPhones(udid) {
|
|
437
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
438
|
+
return this.prisma.deviceSetting.findMany({
|
|
439
|
+
where: { udid },
|
|
440
|
+
select: { udid: true, host: true },
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
302
444
|
findDevice(filter) {
|
|
303
445
|
return __awaiter(this, void 0, void 0, function* () {
|
|
304
446
|
const device = yield this.prisma.device.findFirst({ where: filter });
|
|
@@ -483,21 +625,24 @@ class PrismaHealEtalonStore {
|
|
|
483
625
|
get prisma() {
|
|
484
626
|
return typedi_1.Container.get(prisma_service_1.PrismaService).client;
|
|
485
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* The fingerprint, and its path when it has one. A fingerprint saved without
|
|
630
|
+
* a path (a heal whose element's path couldn't be worked out) keeps the one
|
|
631
|
+
* stored. Through 2.14 the path was never stored, so the Resilio tier,
|
|
632
|
+
* which needs it, never found anything.
|
|
633
|
+
*/
|
|
486
634
|
saveSignature(etalon) {
|
|
487
635
|
return __awaiter(this, void 0, void 0, function* () {
|
|
636
|
+
const path = etalon.path == null ? undefined : JSON.stringify(etalon.path);
|
|
488
637
|
yield this.prisma.locatorEtalon.upsert({
|
|
489
638
|
where: { selector: etalon.selector },
|
|
490
|
-
update: {
|
|
491
|
-
strategy: etalon.strategy,
|
|
492
|
-
attributes: JSON.stringify(etalon.attributes),
|
|
493
|
-
nodeName: etalon.nodeName,
|
|
494
|
-
lastSeen: new Date(),
|
|
495
|
-
},
|
|
639
|
+
update: Object.assign(Object.assign({ strategy: etalon.strategy, attributes: JSON.stringify(etalon.attributes), nodeName: etalon.nodeName }, (path !== undefined ? { path } : {})), { lastSeen: new Date() }),
|
|
496
640
|
create: {
|
|
497
641
|
selector: etalon.selector,
|
|
498
642
|
strategy: etalon.strategy,
|
|
499
643
|
attributes: JSON.stringify(etalon.attributes),
|
|
500
644
|
nodeName: etalon.nodeName,
|
|
645
|
+
path,
|
|
501
646
|
lastSeen: new Date(),
|
|
502
647
|
},
|
|
503
648
|
});
|
|
@@ -510,7 +655,16 @@ class PrismaHealEtalonStore {
|
|
|
510
655
|
});
|
|
511
656
|
if (!etalon)
|
|
512
657
|
return null;
|
|
513
|
-
|
|
658
|
+
let path = null;
|
|
659
|
+
if (etalon.path) {
|
|
660
|
+
try {
|
|
661
|
+
path = JSON.parse(etalon.path);
|
|
662
|
+
}
|
|
663
|
+
catch (_a) {
|
|
664
|
+
path = null;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return Object.assign(Object.assign({}, etalon), { attributes: JSON.parse(etalon.attributes), path, lastSeen: etalon.lastSeen.getTime() });
|
|
514
668
|
});
|
|
515
669
|
}
|
|
516
670
|
}
|