@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
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.SessionDeviceLogs = exports.WRITE_BATCH = exports.FLUSH_INTERVAL_MS = void 0;
|
|
22
|
+
const typedi_1 = require("typedi");
|
|
23
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
24
|
+
const prisma_1 = require("../../prisma");
|
|
25
|
+
const PluginContext_1 = require("../../PluginContext");
|
|
26
|
+
const localDeviceHosts_1 = require("../../device-managers/localDeviceHosts");
|
|
27
|
+
const LogcatStreamService_1 = require("../../device-managers/android/LogcatStreamService");
|
|
28
|
+
const adbForPhone_1 = require("../network/adbForPhone");
|
|
29
|
+
const deviceLogBook_1 = require("./deviceLogBook");
|
|
30
|
+
/** How often a session's new lines are written. */
|
|
31
|
+
exports.FLUSH_INTERVAL_MS = 5000;
|
|
32
|
+
/** Lines per write, and how many waiting lines start one before the interval. */
|
|
33
|
+
exports.WRITE_BATCH = 500;
|
|
34
|
+
/** The phone's `date`, read once a session; one that hangs this long is unknown. */
|
|
35
|
+
const CLOCK_TIMEOUT_MS = 5000;
|
|
36
|
+
/** Waits before opening the phone's log stream again: doubling from the first, up to the last. */
|
|
37
|
+
const RETRY_FIRST_MS = 1000;
|
|
38
|
+
const RETRY_MAX_MS = 30000;
|
|
39
|
+
/**
|
|
40
|
+
* The session page's Device logs for a session on this server's own Android
|
|
41
|
+
* phone: its lines from start to end, each once, with its own time and level,
|
|
42
|
+
* up to a limit (DeviceLogBook).
|
|
43
|
+
*
|
|
44
|
+
* The session listens to the phone's log stream (LogcatStreamService, one
|
|
45
|
+
* `adb logcat` per phone, which the device page's Logs viewer may share) from
|
|
46
|
+
* EventManager's start, once the session's row exists, to its stop, which
|
|
47
|
+
* every ending reaches. While it listens, the stream has a viewer, so its idle
|
|
48
|
+
* stop leaves it running; afterwards it stops 30 s after its last viewer, as
|
|
49
|
+
* ever. The session never stops or restarts it. If the stream ends (the phone
|
|
50
|
+
* restarted, or was unplugged), the session opens it again and notes the gap.
|
|
51
|
+
*
|
|
52
|
+
* Until 2.14 each recorded command dumped the last 500 lines (`logcat -d`),
|
|
53
|
+
* kept the last 100 and skipped as many as the previous dump had given, so
|
|
54
|
+
* after the first command nothing more was ever saved, each line stamped with
|
|
55
|
+
* the time it was saved.
|
|
56
|
+
*/
|
|
57
|
+
let SessionDeviceLogs = class SessionDeviceLogs {
|
|
58
|
+
constructor() {
|
|
59
|
+
this.log = logger_1.default.scope('DeviceLogs');
|
|
60
|
+
this.running = new Map();
|
|
61
|
+
}
|
|
62
|
+
/** Whether a session on this phone is recorded: an Android phone (or emulator) this server drives. */
|
|
63
|
+
appliesTo(device) {
|
|
64
|
+
var _a;
|
|
65
|
+
if (!device || device.cloud)
|
|
66
|
+
return false;
|
|
67
|
+
if (String((_a = device.platform) !== null && _a !== void 0 ? _a : '').toLowerCase() !== 'android')
|
|
68
|
+
return false;
|
|
69
|
+
// A node's phone isn't reachable with this server's adb; the node has
|
|
70
|
+
// the session, and no row for it.
|
|
71
|
+
const ctx = this.context();
|
|
72
|
+
return (0, localDeviceHosts_1.isOwnDevice)((0, localDeviceHosts_1.localDeviceHosts)(ctx.pluginArgs, ctx.port), ctx.nodeId, device);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Starts recording the session's device log. Resolves once it listens to
|
|
76
|
+
* the phone's stream (or is waiting to try again); callers needn't wait.
|
|
77
|
+
*/
|
|
78
|
+
start({ sessionId, device, since }) {
|
|
79
|
+
var _a, _b;
|
|
80
|
+
if (this.running.has(sessionId) || !this.appliesTo(device))
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
const entry = {
|
|
83
|
+
udid: device.udid,
|
|
84
|
+
buffer: [],
|
|
85
|
+
flushTimer: setInterval(() => void this.write(sessionId, entry), this.flushIntervalMs()),
|
|
86
|
+
writing: Promise.resolve(),
|
|
87
|
+
lastCreatedAt: 0,
|
|
88
|
+
attempts: 0,
|
|
89
|
+
stopped: false,
|
|
90
|
+
};
|
|
91
|
+
(_b = (_a = entry.flushTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
92
|
+
this.running.set(sessionId, entry);
|
|
93
|
+
this.log.info(`[${sessionId}] Recording the device log of ${device.udid}`);
|
|
94
|
+
return this.open(sessionId, entry, since !== null && since !== void 0 ? since : Date.now()).catch((err) => { var _a; return this.log.warn(`[${sessionId}] Device log not recorded: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`); });
|
|
95
|
+
}
|
|
96
|
+
/** Stops listening and writes what is left. Idempotent. */
|
|
97
|
+
stop(sessionId) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
var _a;
|
|
100
|
+
const entry = this.running.get(sessionId);
|
|
101
|
+
if (!entry)
|
|
102
|
+
return;
|
|
103
|
+
this.running.delete(sessionId);
|
|
104
|
+
entry.stopped = true;
|
|
105
|
+
clearInterval(entry.flushTimer);
|
|
106
|
+
if (entry.retryTimer)
|
|
107
|
+
clearTimeout(entry.retryTimer);
|
|
108
|
+
(_a = entry.remove) === null || _a === void 0 ? void 0 : _a.call(entry);
|
|
109
|
+
entry.remove = undefined;
|
|
110
|
+
if (entry.book)
|
|
111
|
+
entry.buffer.push(...entry.book.finish(new Date()));
|
|
112
|
+
yield this.write(sessionId, entry);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** Whether the session is recorded here. */
|
|
116
|
+
isRecording(sessionId) {
|
|
117
|
+
return this.running.has(sessionId);
|
|
118
|
+
}
|
|
119
|
+
open(sessionId, entry, since) {
|
|
120
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
121
|
+
var _a;
|
|
122
|
+
let clock = null;
|
|
123
|
+
try {
|
|
124
|
+
clock = yield this.readClock(entry.udid);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
this.log.debug(`[${sessionId}] Can't read the clock of ${entry.udid}: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
128
|
+
}
|
|
129
|
+
if (!clock) {
|
|
130
|
+
this.log.warn(`[${sessionId}] The clock of ${entry.udid} is unknown; its log lines are read as this server's time`);
|
|
131
|
+
}
|
|
132
|
+
if (entry.stopped)
|
|
133
|
+
return;
|
|
134
|
+
entry.book = new deviceLogBook_1.DeviceLogBook({ since, clock: clock !== null && clock !== void 0 ? clock : deviceLogBook_1.UNKNOWN_CLOCK });
|
|
135
|
+
yield this.listen(sessionId, entry);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
listen(sessionId, entry) {
|
|
139
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
140
|
+
var _a;
|
|
141
|
+
entry.retryTimer = undefined;
|
|
142
|
+
if (entry.stopped)
|
|
143
|
+
return;
|
|
144
|
+
let mux;
|
|
145
|
+
try {
|
|
146
|
+
mux = yield this.streams().start(entry.udid);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (entry.attempts === 0) {
|
|
150
|
+
this.log.warn(`[${sessionId}] Can't open the device log of ${entry.udid}: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}. Trying again.`);
|
|
151
|
+
}
|
|
152
|
+
this.retry(sessionId, entry);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Stopped while the stream started: leave it to its own idle stop.
|
|
156
|
+
if (entry.stopped)
|
|
157
|
+
return;
|
|
158
|
+
const book = entry.book;
|
|
159
|
+
let delivered = false;
|
|
160
|
+
const remove = mux.addClient((rec) => {
|
|
161
|
+
if (rec.synthetic)
|
|
162
|
+
return;
|
|
163
|
+
if (!delivered) {
|
|
164
|
+
delivered = true;
|
|
165
|
+
entry.attempts = 0;
|
|
166
|
+
}
|
|
167
|
+
const rows = book.add(rec);
|
|
168
|
+
if (rows.length === 0)
|
|
169
|
+
return;
|
|
170
|
+
entry.buffer.push(...rows);
|
|
171
|
+
if (entry.buffer.length >= exports.WRITE_BATCH)
|
|
172
|
+
void this.write(sessionId, entry);
|
|
173
|
+
},
|
|
174
|
+
// Never refused: what the session keeps is bounded by its book.
|
|
175
|
+
() => true, () => {
|
|
176
|
+
if (entry.remove === remove)
|
|
177
|
+
entry.remove = undefined;
|
|
178
|
+
if (entry.stopped)
|
|
179
|
+
return;
|
|
180
|
+
const note = book.interrupted(new Date());
|
|
181
|
+
// A stream that delivered nothing (a phone that's gone) isn't noted
|
|
182
|
+
// again on every try.
|
|
183
|
+
if (delivered) {
|
|
184
|
+
entry.buffer.push(...note);
|
|
185
|
+
this.log.info(`[${sessionId}] The device log of ${entry.udid} ended; opening it again`);
|
|
186
|
+
}
|
|
187
|
+
this.retry(sessionId, entry);
|
|
188
|
+
});
|
|
189
|
+
entry.remove = remove;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
retry(sessionId, entry) {
|
|
193
|
+
var _a, _b;
|
|
194
|
+
if (entry.stopped || entry.retryTimer)
|
|
195
|
+
return;
|
|
196
|
+
entry.attempts += 1;
|
|
197
|
+
const delay = Math.min(this.retryFirstMs() * 2 ** (entry.attempts - 1), RETRY_MAX_MS);
|
|
198
|
+
entry.retryTimer = setTimeout(() => void this.listen(sessionId, entry), delay);
|
|
199
|
+
(_b = (_a = entry.retryTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Writes what is waiting, WRITE_BATCH rows at a time. On a failure the rows
|
|
203
|
+
* wait for the next write: the book bounds how many there can be.
|
|
204
|
+
*/
|
|
205
|
+
write(sessionId, entry) {
|
|
206
|
+
entry.writing = entry.writing.then(() => __awaiter(this, void 0, void 0, function* () {
|
|
207
|
+
var _a;
|
|
208
|
+
while (entry.buffer.length > 0) {
|
|
209
|
+
const batch = entry.buffer.splice(0, exports.WRITE_BATCH);
|
|
210
|
+
// The session's rows are read in `createdAt` order, so each is a
|
|
211
|
+
// millisecond after the one before: the lines of one moment (a stack
|
|
212
|
+
// trace's) keep their order whatever the database.
|
|
213
|
+
const first = Math.max(Date.now(), entry.lastCreatedAt + 1);
|
|
214
|
+
try {
|
|
215
|
+
yield this.writeLines(sessionId, batch, first);
|
|
216
|
+
entry.lastCreatedAt = first + batch.length - 1;
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
entry.buffer.unshift(...batch);
|
|
220
|
+
this.log.warn(`[${sessionId}] Writing ${batch.length} device log lines failed: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}));
|
|
225
|
+
return entry.writing;
|
|
226
|
+
}
|
|
227
|
+
// ---------------------------------------------------------------------
|
|
228
|
+
// Seams. Overridden by tests; the defaults are the real thing.
|
|
229
|
+
// ---------------------------------------------------------------------
|
|
230
|
+
context() {
|
|
231
|
+
return typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
232
|
+
}
|
|
233
|
+
streams() {
|
|
234
|
+
return typedi_1.Container.get(LogcatStreamService_1.LogcatStreamService);
|
|
235
|
+
}
|
|
236
|
+
flushIntervalMs() {
|
|
237
|
+
return exports.FLUSH_INTERVAL_MS;
|
|
238
|
+
}
|
|
239
|
+
retryFirstMs() {
|
|
240
|
+
return RETRY_FIRST_MS;
|
|
241
|
+
}
|
|
242
|
+
/** The phone's clock against this server's, or null if its answer isn't one. */
|
|
243
|
+
readClock(udid) {
|
|
244
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
245
|
+
const adb = yield (0, adbForPhone_1.adbForPhone)(udid);
|
|
246
|
+
const asked = Date.now();
|
|
247
|
+
// One word: the phone's shell would take a second one as a time to set.
|
|
248
|
+
const out = yield adb.adbExec(['-s', udid, 'shell', deviceLogBook_1.DEVICE_CLOCK_COMMAND], {
|
|
249
|
+
timeout: CLOCK_TIMEOUT_MS,
|
|
250
|
+
});
|
|
251
|
+
return (0, deviceLogBook_1.parseDeviceClock)(String(out !== null && out !== void 0 ? out : ''), (asked + Date.now()) / 2);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
writeLines(sessionId, lines, firstCreatedAt) {
|
|
255
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
256
|
+
yield prisma_1.prisma.log.createMany({
|
|
257
|
+
data: lines.map((line, i) => ({
|
|
258
|
+
session_id: sessionId,
|
|
259
|
+
log_type: 'DEVICE',
|
|
260
|
+
message: line.message,
|
|
261
|
+
timestamp: line.timestamp,
|
|
262
|
+
createdAt: new Date(firstCreatedAt + i),
|
|
263
|
+
})),
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
exports.SessionDeviceLogs = SessionDeviceLogs;
|
|
269
|
+
exports.SessionDeviceLogs = SessionDeviceLogs = __decorate([
|
|
270
|
+
(0, typedi_1.Service)()
|
|
271
|
+
], SessionDeviceLogs);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeviceLogBook = exports.DEVICE_CLOCK_COMMAND = exports.UNKNOWN_CLOCK = exports.WINDOW_SLACK_MS = exports.DEVICE_LOG_ERROR_LIMIT = exports.DEVICE_LOG_LINE_LIMIT = void 0;
|
|
4
|
+
exports.parseDeviceClock = parseDeviceClock;
|
|
5
|
+
exports.formatThreadtime = formatThreadtime;
|
|
6
|
+
const logcatParse_1 = require("./logcatParse");
|
|
7
|
+
/**
|
|
8
|
+
* Which of a phone's log records a session keeps, and as what. Pure: the
|
|
9
|
+
* service (SessionDeviceLogs) feeds it the records of the phone's shared log
|
|
10
|
+
* stream and writes what it returns.
|
|
11
|
+
*
|
|
12
|
+
* - **The session's lines only.** The stream starts with up to 2000 lines of
|
|
13
|
+
* history (`-T`, or the multiplexer's replay to a late joiner), so a line
|
|
14
|
+
* counts from shortly before the phone was given to the session.
|
|
15
|
+
* - **Its own time, on this server's clock.** A line is stamped with the
|
|
16
|
+
* moment the phone logged it, moved by the phone's clock offset, so it
|
|
17
|
+
* lines up with the session's commands; its text keeps the time the phone
|
|
18
|
+
* printed. A phone's clock is often seconds off, and one with no network
|
|
19
|
+
* time can be minutes off.
|
|
20
|
+
* - **Each line once.** A stream delivers a record once. When the stream
|
|
21
|
+
* ends mid-session and is started again, its history overlaps what was
|
|
22
|
+
* kept, so a resumed stream counts only lines after the newest one seen,
|
|
23
|
+
* compared by time and, at that same millisecond, by content.
|
|
24
|
+
* - **Bounded.** DEVICE_LOG_LINE_LIMIT lines of any level, then errors only
|
|
25
|
+
* (a crash's "FATAL EXCEPTION" is an error) up to DEVICE_LOG_ERROR_LIMIT
|
|
26
|
+
* more, each step said in the log itself.
|
|
27
|
+
*/
|
|
28
|
+
/** Lines of any level a session keeps. */
|
|
29
|
+
exports.DEVICE_LOG_LINE_LIMIT = 10000;
|
|
30
|
+
/** Errors (E and F) kept after DEVICE_LOG_LINE_LIMIT; nothing more after them. */
|
|
31
|
+
exports.DEVICE_LOG_ERROR_LIMIT = 2000;
|
|
32
|
+
/**
|
|
33
|
+
* How long before the phone was given to the session a line may be and still
|
|
34
|
+
* count: the phone's clock offset, read once, is only about that good.
|
|
35
|
+
*/
|
|
36
|
+
exports.WINDOW_SLACK_MS = 2000;
|
|
37
|
+
/** A phone whose clock couldn't be read: taken to be this server's. */
|
|
38
|
+
exports.UNKNOWN_CLOCK = { zoneShiftMs: 0, skewMs: 0 };
|
|
39
|
+
const QUARTER_HOUR_MS = 15 * 60000;
|
|
40
|
+
/**
|
|
41
|
+
* The phone's `date` command for {@link parseDeviceClock}: seconds since the
|
|
42
|
+
* epoch, nanoseconds, and the local time logcat prints, at one instant, with
|
|
43
|
+
* no spaces (it goes through the phone's shell as one word). An older
|
|
44
|
+
* toybox prints no nanoseconds.
|
|
45
|
+
*/
|
|
46
|
+
exports.DEVICE_CLOCK_COMMAND = 'date +%s.%N.%m-%d.%H:%M:%S';
|
|
47
|
+
/**
|
|
48
|
+
* The phone's clock from {@link DEVICE_CLOCK_COMMAND}'s output, or null if it
|
|
49
|
+
* isn't that. `hostNowMs` is this server's time when the phone answered.
|
|
50
|
+
*
|
|
51
|
+
* The local time is read the way parseThreadtimeLine reads a log line, so the
|
|
52
|
+
* difference to the phone's epoch is exactly the shift its lines need,
|
|
53
|
+
* whatever either time zone is. Rounded to a quarter hour: time zones differ
|
|
54
|
+
* by whole quarters.
|
|
55
|
+
*/
|
|
56
|
+
function parseDeviceClock(output, hostNowMs) {
|
|
57
|
+
const m = /(\d{9,})\.(\S*?)\.(\d{2})-(\d{2})\.(\d{2}):(\d{2}):(\d{2})/.exec(output);
|
|
58
|
+
if (!m)
|
|
59
|
+
return null;
|
|
60
|
+
const [, secs, nanos, mo, d, h, mi, s] = m;
|
|
61
|
+
const asLogged = (0, logcatParse_1.parseThreadtimeLine)(`${mo}-${d} ${h}:${mi}:${s}.000 0 0 I clock: now`, new Date(hostNowMs));
|
|
62
|
+
if (!asLogged)
|
|
63
|
+
return null;
|
|
64
|
+
// Without nanoseconds, the phone's instant was within the second after `%s`.
|
|
65
|
+
const fraction = /^\d{9}$/.test(nanos) ? Math.floor(Number(nanos) / 1e6) : 500;
|
|
66
|
+
const phoneNow = Number(secs) * 1000 + fraction;
|
|
67
|
+
return {
|
|
68
|
+
zoneShiftMs: Math.round((phoneNow - asLogged.ts) / QUARTER_HOUR_MS) * QUARTER_HOUR_MS,
|
|
69
|
+
skewMs: hostNowMs - phoneNow,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
73
|
+
/**
|
|
74
|
+
* A record as logcat's threadtime format prints it (logprint.cpp,
|
|
75
|
+
* `"%s %5d %5d %c %-8s: "`): the time as the phone printed it, then the pid,
|
|
76
|
+
* the tid, the level, the tag and the message. The dashboard reads the level
|
|
77
|
+
* from this text (log-derive.ts), and it is what Android developers know.
|
|
78
|
+
*/
|
|
79
|
+
function formatThreadtime(rec) {
|
|
80
|
+
// `ts` was built from the printed fields as this server's local time, so
|
|
81
|
+
// its local fields are the printed ones.
|
|
82
|
+
const t = new Date(rec.ts);
|
|
83
|
+
const time = `${pad(t.getMonth() + 1)}-${pad(t.getDate())} ` +
|
|
84
|
+
`${pad(t.getHours())}:${pad(t.getMinutes())}:${pad(t.getSeconds())}.${pad(t.getMilliseconds(), 3)}`;
|
|
85
|
+
const pid = String(rec.pid).padStart(5);
|
|
86
|
+
const tid = String(rec.tid).padStart(5);
|
|
87
|
+
return `${time} ${pid} ${tid} ${rec.level} ${rec.tag.padEnd(8)}: ${rec.message}`;
|
|
88
|
+
}
|
|
89
|
+
const count = (n) => n.toLocaleString('en-US');
|
|
90
|
+
const keyOf = (r) => `${r.pid} ${r.tid} ${r.level} ${r.tag} ${r.message}`;
|
|
91
|
+
const isError = (r) => r.level === 'E' || r.level === 'F';
|
|
92
|
+
class DeviceLogBook {
|
|
93
|
+
constructor(opts) {
|
|
94
|
+
var _a, _b;
|
|
95
|
+
this.kept = 0;
|
|
96
|
+
this.leftOut = 0;
|
|
97
|
+
this.limitNoted = false;
|
|
98
|
+
this.clock = opts.clock;
|
|
99
|
+
this.lineLimit = (_a = opts.lineLimit) !== null && _a !== void 0 ? _a : exports.DEVICE_LOG_LINE_LIMIT;
|
|
100
|
+
this.errorLimit = (_b = opts.errorLimit) !== null && _b !== void 0 ? _b : exports.DEVICE_LOG_ERROR_LIMIT;
|
|
101
|
+
// A record at `ts` was logged at ts + zoneShift + skew by this server's
|
|
102
|
+
// clock (timeOf); it counts from WINDOW_SLACK_MS before `since`.
|
|
103
|
+
const first = opts.since - exports.WINDOW_SLACK_MS - this.clock.zoneShiftMs - this.clock.skewMs;
|
|
104
|
+
this.floor = { ts: Math.ceil(first), keys: new Set() };
|
|
105
|
+
}
|
|
106
|
+
/** The rows a record adds: none, its own, or a note before it. */
|
|
107
|
+
add(rec) {
|
|
108
|
+
// The stream's own notes (a slow viewer's dropped lines, its end) aren't
|
|
109
|
+
// the phone's; the service notes an interruption itself.
|
|
110
|
+
if (rec.synthetic)
|
|
111
|
+
return [];
|
|
112
|
+
const key = keyOf(rec);
|
|
113
|
+
if (rec.ts < this.floor.ts || (rec.ts === this.floor.ts && this.floor.keys.has(key))) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
if (!this.newest || rec.ts > this.newest.ts)
|
|
117
|
+
this.newest = { ts: rec.ts, keys: new Set([key]) };
|
|
118
|
+
else if (rec.ts === this.newest.ts)
|
|
119
|
+
this.newest.keys.add(key);
|
|
120
|
+
const out = [];
|
|
121
|
+
if (this.kept >= this.lineLimit && !this.limitNoted) {
|
|
122
|
+
this.limitNoted = true;
|
|
123
|
+
out.push(this.note(`This session reached ${count(this.lineLimit)} device log lines. ` +
|
|
124
|
+
'From here on, only errors are kept.', this.timeOf(rec)));
|
|
125
|
+
}
|
|
126
|
+
const room = this.kept < this.lineLimit || (isError(rec) && this.kept < this.lineLimit + this.errorLimit);
|
|
127
|
+
if (room) {
|
|
128
|
+
this.kept += 1;
|
|
129
|
+
out.push({ message: formatThreadtime(rec), timestamp: this.timeOf(rec) });
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
this.leftOut += 1;
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The stream ended while the session runs. The next one starts with
|
|
138
|
+
* history, so it may deliver only what is newer than what was seen.
|
|
139
|
+
*/
|
|
140
|
+
interrupted(at) {
|
|
141
|
+
if (this.newest)
|
|
142
|
+
this.floor = { ts: this.newest.ts, keys: new Set(this.newest.keys) };
|
|
143
|
+
return [this.note("The phone's log was interrupted. Lines may be missing from here.", at)];
|
|
144
|
+
}
|
|
145
|
+
/** The session ended: what was left out, if anything. */
|
|
146
|
+
finish(at) {
|
|
147
|
+
if (this.leftOut === 0)
|
|
148
|
+
return [];
|
|
149
|
+
const lines = this.leftOut === 1 ? '1 device log line was' : `${count(this.leftOut)} device log lines were`;
|
|
150
|
+
return [this.note(`${lines} left out to keep this session's log within its limit.`, at)];
|
|
151
|
+
}
|
|
152
|
+
/** Lines kept so far. */
|
|
153
|
+
get keptLines() {
|
|
154
|
+
return this.kept;
|
|
155
|
+
}
|
|
156
|
+
timeOf(rec) {
|
|
157
|
+
return new Date(rec.ts + this.clock.zoneShiftMs + this.clock.skewMs);
|
|
158
|
+
}
|
|
159
|
+
note(text, at) {
|
|
160
|
+
return { message: `Xenon: ${text}`, timestamp: at };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.DeviceLogBook = DeviceLogBook;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.AndroidRadios = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* A phone's Wi-Fi and mobile data, through the resolved adb (`adbExec`: no
|
|
15
|
+
* shell, no bare `adb`).
|
|
16
|
+
*/
|
|
17
|
+
class AndroidRadios {
|
|
18
|
+
constructor(getAdb) {
|
|
19
|
+
this.getAdb = getAdb;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* `wifi_on` is 1 or 2 when Wi-Fi is on (2: on in spite of airplane mode),
|
|
23
|
+
* 0 or 3 when it is off. `mobile_data` is 1 or 0. Anything else, or a read
|
|
24
|
+
* that fails, is null: "don't know".
|
|
25
|
+
*/
|
|
26
|
+
read(udid) {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
const adb = yield this.getAdb(udid);
|
|
29
|
+
const get = (name) => __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
try {
|
|
31
|
+
const out = yield adb.adbExec(['-s', udid, 'shell', 'settings', 'get', 'global', name]);
|
|
32
|
+
return String(out !== null && out !== void 0 ? out : '').trim();
|
|
33
|
+
}
|
|
34
|
+
catch (_a) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
const wifi = yield get('wifi_on');
|
|
39
|
+
const data = yield get('mobile_data');
|
|
40
|
+
return {
|
|
41
|
+
wifiOn: wifi === '1' || wifi === '2' ? true : wifi === '0' || wifi === '3' ? false : null,
|
|
42
|
+
dataOn: data === '1' ? true : data === '0' ? false : null,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Mobile data, then Wi-Fi, off. */
|
|
47
|
+
turnOff(udid) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
const adb = yield this.getAdb(udid);
|
|
50
|
+
yield adb.adbExec(['-s', udid, 'shell', 'svc', 'data', 'disable']);
|
|
51
|
+
yield adb.adbExec(['-s', udid, 'shell', 'svc', 'wifi', 'disable']);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Turns on the ones asked for; leaves the others as they are. */
|
|
55
|
+
turnOn(udid, which) {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
if (!which.data && !which.wifi)
|
|
58
|
+
return;
|
|
59
|
+
const adb = yield this.getAdb(udid);
|
|
60
|
+
if (which.data)
|
|
61
|
+
yield adb.adbExec(['-s', udid, 'shell', 'svc', 'data', 'enable']);
|
|
62
|
+
if (which.wifi)
|
|
63
|
+
yield adb.adbExec(['-s', udid, 'shell', 'svc', 'wifi', 'enable']);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** What to turn back on after Offline: what was on, and what nobody knew about. */
|
|
67
|
+
static toRestore(prior) {
|
|
68
|
+
return { data: prior.dataOn !== false, wifi: prior.wifiOn !== false };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.AndroidRadios = AndroidRadios;
|