@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.
Files changed (350) hide show
  1. package/README.md +17 -8
  2. package/lib/package.json +5 -5
  3. package/lib/public/assets/{AnnotationOverlay-BXruClDk.js → AnnotationOverlay-Btq3dZRI.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-qTS4LPLC.js → ApiKeyGate-C9OoAgg_.js} +1 -1
  5. package/lib/public/assets/DeviceMosaicView-DBC_xczB.js +2 -0
  6. package/lib/public/assets/{EmptyState-Cr17PdDv.js → EmptyState-DqWU2-vM.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-Bvn08svv.js → FieldGroup-CveQ3xrA.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-DDSO8aDf.js → FilterMenu-BfKDSfHz.js} +1 -1
  9. package/lib/public/assets/{Menu-B-Qn2X3T.js → Menu-CtdCybzA.js} +1 -1
  10. package/lib/public/assets/{Modal-Cg9V5JYU.js → Modal-Bsvj1tm2.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-BTUs9VTW.js → RecordingPage-CRofOiyx.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-DYcfxITK.js → RecordingsPage-jNQHb2IR.js} +2 -2
  13. package/lib/public/assets/{SegmentedControl-Zy7suhT5.js → SegmentedControl-C8HtTha-.js} +1 -1
  14. package/lib/public/assets/{SettingCard-CWYmycoe.js → SettingCard-D8K1LZNo.js} +1 -1
  15. package/lib/public/assets/{Table-io9fwa98.js → Table-zNTX92Pn.js} +1 -1
  16. package/lib/public/assets/{activity-Bdf_uIWl.js → activity-CJ8LwZq9.js} +1 -1
  17. package/lib/public/assets/ai-settings-CgxQZqrc.js +16 -0
  18. package/lib/public/assets/{api-keys-CPqtVpm3.js → api-keys-ECpo-zTo.js} +1 -1
  19. package/lib/public/assets/{apps-BgZan0H9.js → apps-BjGymyf3.js} +2 -2
  20. package/lib/public/assets/{arrow-left-B27ckB31.js → arrow-left-BWfCbeHP.js} +1 -1
  21. package/lib/public/assets/{arrow-right-DOF8ITCz.js → arrow-right-DC7UGf_n.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-eFG5KTFS.js → arrow-up-right-BOjwybDk.js} +1 -1
  23. package/lib/public/assets/{auth-shell-DlC3_27j.js → auth-shell-CxMTo9aH.js} +2 -2
  24. package/lib/public/assets/{builds-page-BuXpPfKo.js → builds-page-DOCm4HkA.js} +1 -1
  25. package/lib/public/assets/{button-fIW8owMI.js → button-Cg08-EMT.js} +1 -1
  26. package/lib/public/assets/{calendar-WNjmrF-a.js → calendar-CA1ikLJs.js} +1 -1
  27. package/lib/public/assets/{check-BdTh1Sq7.js → check-BMTaH72W.js} +1 -1
  28. package/lib/public/assets/{chevron-right-B4BKlsqk.js → chevron-right-B2QOv9Nx.js} +1 -1
  29. package/lib/public/assets/{circle-check-zJU80_EF.js → circle-check-CYXBoUqT.js} +1 -1
  30. package/lib/public/assets/{circle-x-5Uwquo0h.js → circle-x-DTwnSAnl.js} +1 -1
  31. package/lib/public/assets/{clock-BtUprDsZ.js → clock-C8_HViQP.js} +1 -1
  32. package/lib/public/assets/{copy-DGSbZI3u.js → copy-Be7LNMXV.js} +1 -1
  33. package/lib/public/assets/derive-UnLRp4r3.js +1 -0
  34. package/lib/public/assets/device-explorer-BOqtfGXz.js +282 -0
  35. package/lib/public/assets/{download-WG6l7BgD.js → download-B56lwxWP.js} +1 -1
  36. package/lib/public/assets/forgot-password-CCU6bzLn.js +1 -0
  37. package/lib/public/assets/h264Stream-C-OtmIuk.js +26 -0
  38. package/lib/public/assets/index-B9KQ6LsY.js +15 -0
  39. package/lib/public/assets/{index-DI1fGIyj.js → index-BOxe43p1.js} +5 -5
  40. package/lib/public/assets/{index-HDQVe1gi.js → index-CfxsUslp.js} +1 -1
  41. package/lib/public/assets/{input-BjDRKCea.js → input-CZHFjbSg.js} +1 -1
  42. package/lib/public/assets/{line-chart-C0zdgd0v.js → line-chart-Gl_Dz3X_.js} +1 -1
  43. package/lib/public/assets/{list-checks-BdW5CjM8.js → list-checks-CCgjeyGY.js} +1 -1
  44. package/lib/public/assets/{lock-Dy3L4I8E.js → lock-CkOwgDcx.js} +1 -1
  45. package/lib/public/assets/{login-BHbY9CLG.js → login-D5cc4w7M.js} +1 -1
  46. package/lib/public/assets/maintenance-settings-t7qr3fvX.js +6 -0
  47. package/lib/public/assets/{monitor-8xjkeHHi.js → monitor-DEJixKQO.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-CXdKMXQ8.js → mouse-pointer-2-CtOrk4M8.js} +1 -1
  49. package/lib/public/assets/{network-Cj8JOKON.js → network-Dii9qdvk.js} +1 -1
  50. package/lib/public/assets/{overview-CTg7F0im.js → overview-rolAACrO.js} +1 -1
  51. package/lib/public/assets/{page-header--aTyJXq6.js → page-header-COQZxJfR.js} +1 -1
  52. package/lib/public/assets/{play-B-FodgHV.js → play-DrN1Se_R.js} +1 -1
  53. package/lib/public/assets/{plus-CsIk10Cq.js → plus-5aq-9uRx.js} +1 -1
  54. package/lib/public/assets/{profile-page-DkFKkMfl.js → profile-page-6i5kY6kF.js} +1 -1
  55. package/lib/public/assets/{reset-password-I2-zqLlI.js → reset-password-sp2IBrc7.js} +1 -1
  56. package/lib/public/assets/runbook-page-DJDjeopU.js +253 -0
  57. package/lib/public/assets/{select-DvCSv25z.js → select-Bpyn7rWO.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-BLVfMJs_.js → selector-detail-redirect-BH74vmKv.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-oqadRokn.js → selector-health-page-Cw_g218H.js} +2 -2
  60. package/lib/public/assets/session-detail-page-AZJE6XXE.js +4 -0
  61. package/lib/public/assets/settings-C3uyGhyc.js +6 -0
  62. package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
  63. package/lib/public/assets/{stat-tile-DV9Ix-4O.js → stat-tile-xVqAKIlI.js} +1 -1
  64. package/lib/public/assets/{tablet-6b_SEN2X.js → tablet-BxUScAeO.js} +1 -1
  65. package/lib/public/assets/{teams-BjZyfuHh.js → teams-B5ljGkY2.js} +1 -1
  66. package/lib/public/assets/{trash-2-CTZpPSdd.js → trash-2-B701i8sy.js} +1 -1
  67. package/lib/public/assets/{upload-P3XzwQUb.js → upload-C2gr44RS.js} +1 -1
  68. package/lib/public/assets/{use-builds-data-1cOCMf-z.js → use-builds-data-NeQOFEoy.js} +1 -1
  69. package/lib/public/assets/{use-password-reset-mode-DgF9w088.js → use-password-reset-mode-DEzJq5ef.js} +1 -1
  70. package/lib/public/assets/{users-DMF9Mk5H.js → users-D_vv_yz4.js} +1 -1
  71. package/lib/public/assets/{users-CiNTKUiz.js → users-MY6eNe6s.js} +1 -1
  72. package/lib/public/assets/{video-off-CHp1KU_T.js → video-off-OK1lNZQn.js} +1 -1
  73. package/lib/public/assets/webhook-settings-BU-C9lBf.js +1 -0
  74. package/lib/public/assets/{zap-DRz0tDq5.js → zap-DO9ry1iz.js} +1 -1
  75. package/lib/public/index.html +1 -1
  76. package/lib/schema.json +23 -25
  77. package/lib/src/XenonCapabilityManager.js +22 -5
  78. package/lib/src/app/dashboardPluginLink.js +117 -0
  79. package/lib/src/app/index.js +13 -34
  80. package/lib/src/app/openapi/control.yaml +73 -20
  81. package/lib/src/app/openapi/grid.yaml +31 -7
  82. package/lib/src/app/openapi/identity.yaml +103 -31
  83. package/lib/src/app/openapi/platform.yaml +165 -39
  84. package/lib/src/app/openapi/sessions.yaml +10 -3
  85. package/lib/src/app/routers/apikeys.js +14 -2
  86. package/lib/src/app/routers/auth.js +29 -5
  87. package/lib/src/app/routers/control.js +20 -2
  88. package/lib/src/app/routers/dashboard.js +69 -26
  89. package/lib/src/app/routers/grid.js +37 -13
  90. package/lib/src/app/routers/profile.js +21 -0
  91. package/lib/src/app/routers/streamType.js +7 -2
  92. package/lib/src/app/routers/users.js +10 -5
  93. package/lib/src/app/routers/webhook.js +8 -4
  94. package/lib/src/app/swagger.js +2 -0
  95. package/lib/src/app/ws/h264StreamWs.js +10 -1
  96. package/lib/src/config.js +51 -5
  97. package/lib/src/dashboard/commands.js +185 -141
  98. package/lib/src/dashboard/event-manager.js +88 -67
  99. package/lib/src/dashboard/services/failure-analysis-service.js +136 -45
  100. package/lib/src/dashboard/sessionCommandSummary.js +39 -0
  101. package/lib/src/data-service/appleFamily.js +43 -0
  102. package/lib/src/data-service/db.js +11 -0
  103. package/lib/src/data-service/device-service.js +6 -1
  104. package/lib/src/data-service/device-store.js +76 -3
  105. package/lib/src/data-service/deviceClaims.js +37 -0
  106. package/lib/src/data-service/deviceFieldOwners.js +25 -11
  107. package/lib/src/data-service/deviceSettings.js +79 -0
  108. package/lib/src/data-service/prisma-store.js +169 -15
  109. package/lib/src/data-service/web-config-service.js +50 -92
  110. package/lib/src/device-managers/AndroidDeviceManager.js +9 -31
  111. package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
  112. package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
  113. package/lib/src/device-managers/ios/WDAClient.js +44 -43
  114. package/lib/src/device-managers/shellCommands.js +96 -0
  115. package/lib/src/device-utils.js +114 -40
  116. package/lib/src/gateway/defaultGateway.js +1 -1
  117. package/lib/src/gateway/forwardToNode.js +2 -0
  118. package/lib/src/gateway/healReport.js +87 -0
  119. package/lib/src/gateway/sessionGateway.js +12 -2
  120. package/lib/src/generated/client/edge.js +18 -3
  121. package/lib/src/generated/client/index-browser.js +15 -0
  122. package/lib/src/generated/client/index.d.ts +2086 -379
  123. package/lib/src/generated/client/index.js +18 -3
  124. package/lib/src/generated/client/package.json +1 -1
  125. package/lib/src/generated/client/schema.prisma +38 -7
  126. package/lib/src/generated/client/wasm.js +15 -0
  127. package/lib/src/interceptors/CommandInterceptor.js +271 -188
  128. package/lib/src/interceptors/xenonScripts.js +55 -0
  129. package/lib/src/interfaces/IPluginArgs.js +1 -2
  130. package/lib/src/middleware/authMiddleware.js +15 -3
  131. package/lib/src/middleware/commandCaller.js +3 -3
  132. package/lib/src/middleware/verifyCredential.js +58 -1
  133. package/lib/src/plugin.js +23 -4
  134. package/lib/src/prisma.js +1 -1
  135. package/lib/src/scripts/database-check.js +124 -0
  136. package/lib/src/services/AICommandService.js +9 -2
  137. package/lib/src/services/AIService.js +281 -32
  138. package/lib/src/services/CleanupService.js +6 -1
  139. package/lib/src/services/EmailService.js +29 -0
  140. package/lib/src/services/InterceptorService.js +70 -13
  141. package/lib/src/services/NetworkConditioningService.js +85 -88
  142. package/lib/src/services/NotificationService.js +36 -29
  143. package/lib/src/services/OrphanSweeper.js +7 -0
  144. package/lib/src/services/PortAllocator.js +5 -0
  145. package/lib/src/services/SelectorVerificationJob.js +15 -4
  146. package/lib/src/services/ServerManager.js +105 -9
  147. package/lib/src/services/SessionLifecycleService.js +133 -75
  148. package/lib/src/services/ShutdownCoordinator.js +33 -0
  149. package/lib/src/services/SocketServer.js +19 -3
  150. package/lib/src/services/TeamService.js +20 -3
  151. package/lib/src/services/bug-report/video-slice.js +4 -1
  152. package/lib/src/services/device-access/leaseOverride.js +7 -5
  153. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
  154. package/lib/src/services/healing/HealEtalonService.js +3 -2
  155. package/lib/src/services/healing/HealingOrchestrator.js +85 -71
  156. package/lib/src/services/healing/OcrHealingProvider.js +41 -64
  157. package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
  158. package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
  159. package/lib/src/services/healing/fingerprintIdentity.js +126 -0
  160. package/lib/src/services/healing/resilioPath.js +274 -0
  161. package/lib/src/services/interceptor/AndroidProxyAdapter.js +18 -0
  162. package/lib/src/services/interceptor/interceptorOptions.js +44 -0
  163. package/lib/src/services/logcat/SessionDeviceLogs.js +271 -0
  164. package/lib/src/services/logcat/deviceLogBook.js +163 -0
  165. package/lib/src/services/network/AndroidRadios.js +71 -0
  166. package/lib/src/services/network/PhoneNetworkLedger.js +184 -0
  167. package/lib/src/services/network/PhoneNetworkRestore.js +330 -0
  168. package/lib/src/services/network/adbForPhone.js +60 -0
  169. package/lib/src/services/network/phoneNetworkLock.js +20 -0
  170. package/lib/src/services/network/xenonProxy.js +107 -0
  171. package/lib/src/services/ocr/ocrData.js +118 -0
  172. package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
  173. package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
  174. package/lib/src/services/omni-vision/OmniVisionService.js +132 -95
  175. package/lib/src/services/omni-vision/VisionAssertionService.js +28 -25
  176. package/lib/src/services/omni-vision/ocrTextMatch.js +105 -0
  177. package/lib/src/services/omni-vision/screenScale.js +68 -0
  178. package/lib/src/services/passwordResetLink.js +50 -9
  179. package/lib/src/services/passwordResetMode.js +3 -1
  180. package/lib/src/services/publicUrl.js +45 -0
  181. package/lib/src/services/recording/concurrency-gate.js +12 -3
  182. package/lib/src/services/session/sessionIdentity.js +5 -3
  183. package/lib/src/services/session/shutdownReason.js +26 -0
  184. package/lib/src/services/settings/SelfHealingSwitch.js +70 -0
  185. package/lib/src/services/settings/aiEngineSettings.js +219 -0
  186. package/lib/src/services/settings/labSettings.js +149 -0
  187. package/lib/src/services/token/mintedLifetime.js +51 -0
  188. package/lib/src/services/webhookEvents.js +173 -0
  189. package/lib/src/sessions/SessionManager.js +8 -2
  190. package/lib/src/sessions/sessionMemory.js +25 -0
  191. package/lib/test/helpers/fake-adb.js +58 -0
  192. package/lib/test/helpers/fake-ai-provider.js +188 -0
  193. package/lib/test/helpers/fake-device-table.js +63 -24
  194. package/lib/test/helpers/loki-stores.js +33 -0
  195. package/lib/test/helpers/page-sources.js +164 -0
  196. package/lib/test/helpers/scratch-database.js +58 -13
  197. package/lib/test/integration/audit-ingest.spec.js +10 -10
  198. package/lib/test/integration/auth-flow.spec.js +9 -14
  199. package/lib/test/integration/auth-rate-limit.spec.js +6 -3
  200. package/lib/test/integration/bug-report-route.spec.js +22 -2
  201. package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
  202. package/lib/test/integration/forgot-password.spec.js +17 -18
  203. package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
  204. package/lib/test/integration/migration.spec.js +6 -1
  205. package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
  206. package/lib/test/integration/ports-router.spec.js +4 -4
  207. package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
  208. package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
  209. package/lib/test/integration/profile-tokens.spec.js +10 -16
  210. package/lib/test/integration/recordings-router.spec.js +16 -16
  211. package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
  212. package/lib/test/integration/role-matrix.spec.js +4 -9
  213. package/lib/test/integration/sdk-leases-router.spec.js +9 -9
  214. package/lib/test/integration/sdk-version.spec.js +2 -2
  215. package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
  216. package/lib/test/integration/team-membership-migration.spec.js +73 -12
  217. package/lib/test/integration/team-visibility-apps.spec.js +15 -22
  218. package/lib/test/integration/team-visibility-control.spec.js +96 -69
  219. package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
  220. package/lib/test/integration/team-visibility-grid.spec.js +16 -15
  221. package/lib/test/integration/team-visibility-leases.spec.js +8 -11
  222. package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
  223. package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
  224. package/lib/test/integration/users-flow.spec.js +7 -12
  225. package/lib/test/integration/users-lockout.spec.js +5 -3
  226. package/lib/test/unit/EmailService.test.js +66 -0
  227. package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
  228. package/lib/test/unit/TeamService.test.js +3 -0
  229. package/lib/test/unit/XenonCapabilityManager.spec.js +4 -2
  230. package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
  231. package/lib/test/unit/ai-engine-settings.spec.js +338 -0
  232. package/lib/test/unit/ai-provider-switch.spec.js +130 -0
  233. package/lib/test/unit/ai-rate-limit.spec.js +126 -0
  234. package/lib/test/unit/android-h264-viewers.spec.js +272 -0
  235. package/lib/test/unit/android-preview-hold.spec.js +3 -2
  236. package/lib/test/unit/apikeys-router.spec.js +84 -0
  237. package/lib/test/unit/apple-family.spec.js +36 -0
  238. package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
  239. package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
  240. package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
  241. package/lib/test/unit/cleanup-settings-db.spec.js +103 -0
  242. package/lib/test/unit/cleanup-settings.spec.js +244 -0
  243. package/lib/test/unit/dashboard-api-paths.spec.js +101 -0
  244. package/lib/test/unit/dashboard-commands.spec.js +249 -0
  245. package/lib/test/unit/dashboard-events-device-logs.spec.js +207 -0
  246. package/lib/test/unit/dashboard-events-team-scope.spec.js +9 -6
  247. package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
  248. package/lib/test/unit/database-check.spec.js +108 -0
  249. package/lib/test/unit/default-plugin-args.spec.js +33 -0
  250. package/lib/test/unit/device-log-book.spec.js +180 -0
  251. package/lib/test/unit/device-offline-webhook.spec.js +81 -0
  252. package/lib/test/unit/device-setting-columns.spec.js +66 -0
  253. package/lib/test/unit/device-settings-loki.spec.js +87 -0
  254. package/lib/test/unit/device-settings-survive.spec.js +555 -0
  255. package/lib/test/unit/device-sync-lost-update.spec.js +5 -0
  256. package/lib/test/unit/emulator-boot.spec.js +91 -0
  257. package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
  258. package/lib/test/unit/event-log-session-data.spec.js +244 -0
  259. package/lib/test/unit/failure-analysis.spec.js +182 -0
  260. package/lib/test/unit/failure-categories.spec.js +128 -0
  261. package/lib/test/unit/fingerprint-identity.spec.js +433 -0
  262. package/lib/test/unit/h264-multiplexer.spec.js +46 -0
  263. package/lib/test/unit/heal-report.spec.js +71 -0
  264. package/lib/test/unit/healing-instrumentation.spec.js +6 -1
  265. package/lib/test/unit/healing-tier-gate.spec.js +73 -29
  266. package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
  267. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +219 -0
  268. package/lib/test/unit/hub-node-same-host.spec.js +5 -0
  269. package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
  270. package/lib/test/unit/iphone-ipad-only.spec.js +132 -0
  271. package/lib/test/unit/json-logging-option.spec.js +74 -0
  272. package/lib/test/unit/lab-settings.spec.js +218 -0
  273. package/lib/test/unit/lease/lease-override.spec.js +23 -6
  274. package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
  275. package/lib/test/unit/max-sessions.spec.js +240 -0
  276. package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
  277. package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
  278. package/lib/test/unit/ocr-offline.spec.js +213 -0
  279. package/lib/test/unit/ocr-text-match.spec.js +44 -0
  280. package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
  281. package/lib/test/unit/omni-vision-analyze-screen.spec.js +72 -0
  282. package/lib/test/unit/omni-vision-ios-points.spec.js +159 -0
  283. package/lib/test/unit/omni-vision-tap-text.spec.js +129 -0
  284. package/lib/test/unit/omni-vision.spec.js +6 -8
  285. package/lib/test/unit/package-engines.spec.js +27 -0
  286. package/lib/test/unit/partial-config-file.spec.js +14 -0
  287. package/lib/test/unit/passwordResetMode.test.js +10 -2
  288. package/lib/test/unit/phone-network/boot-clean-up.spec.js +64 -0
  289. package/lib/test/unit/phone-network/interceptor-options.spec.js +77 -0
  290. package/lib/test/unit/phone-network/interceptor-proxy-restore.spec.js +145 -0
  291. package/lib/test/unit/phone-network/network-conditioning.spec.js +145 -0
  292. package/lib/test/unit/phone-network/phone-network-restore.spec.js +246 -0
  293. package/lib/test/unit/phone-network/session-end-paths.spec.js +182 -0
  294. package/lib/test/unit/phone-network/session-start.spec.js +110 -0
  295. package/lib/test/unit/phone-network/xenon-proxy.spec.js +69 -0
  296. package/lib/test/unit/plugin.spec.js +3 -3
  297. package/lib/test/unit/profile-router.test.js +122 -1
  298. package/lib/test/unit/queue-requester.spec.js +1 -1
  299. package/lib/test/unit/recording-options.spec.js +181 -0
  300. package/lib/test/unit/reset-link-address.spec.js +266 -0
  301. package/lib/test/unit/reset-link.test.js +10 -4
  302. package/lib/test/unit/resilio-path-healing.spec.js +270 -0
  303. package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
  304. package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
  305. package/lib/test/unit/selector-verification-job.spec.js +13 -13
  306. package/lib/test/unit/self-healing-boot.spec.js +80 -0
  307. package/lib/test/unit/self-healing-setting.spec.js +273 -0
  308. package/lib/test/unit/session-app-resolution.spec.js +5 -1
  309. package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
  310. package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
  311. package/lib/test/unit/session-credential-strip.spec.js +1 -1
  312. package/lib/test/unit/session-device-logs.spec.js +347 -0
  313. package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
  314. package/lib/test/unit/session-failed-webhook.spec.js +170 -0
  315. package/lib/test/unit/session-log-indexes.spec.js +64 -3
  316. package/lib/test/unit/shell-commands.spec.js +133 -0
  317. package/lib/test/unit/socketServer.scopedEmit.spec.js +2 -0
  318. package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
  319. package/lib/test/unit/stream-start-pipelines.spec.js +264 -0
  320. package/lib/test/unit/stream-status-type.spec.js +4 -0
  321. package/lib/test/unit/token-mint-limits.spec.js +165 -0
  322. package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
  323. package/lib/test/unit/verify-credential.spec.js +33 -0
  324. package/lib/test/unit/vision-assertion.spec.js +119 -0
  325. package/lib/test/unit/wda-client-shell.spec.js +133 -0
  326. package/lib/test/unit/web-config-service.spec.js +98 -0
  327. package/lib/test/unit/webhook-delivery.spec.js +24 -0
  328. package/lib/test/unit/webhook-payloads.spec.js +273 -0
  329. package/lib/test/unit/xenon-execute-scripts.spec.js +138 -0
  330. package/lib/tsconfig.tsbuildinfo +1 -1
  331. package/lib/web/src/components/webhook-settings/webhookEventVariables.json +30 -0
  332. package/package.json +5 -5
  333. package/prisma/migrations/20261004140000_keep_device_settings/migration.sql +19 -0
  334. package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
  335. package/prisma/schema.prisma +38 -7
  336. package/schema.json +23 -25
  337. package/scripts/generate-types-from-schema.js +1 -2
  338. package/scripts/lib/always-present-plugin-args.js +4 -1
  339. package/lib/public/assets/BugReportButton-BoGhlI7F.js +0 -15
  340. package/lib/public/assets/DeviceMosaicView-8J0oYqMM.js +0 -2
  341. package/lib/public/assets/ai-settings-Bce5SJob.js +0 -21
  342. package/lib/public/assets/device-explorer-C-KnhMHI.js +0 -279
  343. package/lib/public/assets/forgot-password-DnudqFFo.js +0 -1
  344. package/lib/public/assets/maintenance-settings-zDVlOMVK.js +0 -6
  345. package/lib/public/assets/recording-group-store-WVM3hoXn.js +0 -26
  346. package/lib/public/assets/runbook-page-DJTVCJX7.js +0 -124
  347. package/lib/public/assets/session-detail-page-CGn55Pvl.js +0 -4
  348. package/lib/public/assets/settings-MsVU2ukE.js +0 -6
  349. package/lib/public/assets/webhook-settings-DiS8geIm.js +0 -1
  350. /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /**
3
+ * Xenon's execute scripts: `driver.execute('xenon: <name>', ...)`, or `xe:`.
4
+ *
5
+ * Who answers them:
6
+ * - The five session-details commands (SESSION_DETAILS_COMMANDS in
7
+ * dashboard/commands.ts) are answered by the server that keeps the
8
+ * session's record: a hub, for a session on a node's phone, when its
9
+ * dashboard is on (its gateway's before-hook), otherwise the server that
10
+ * drives the phone.
11
+ * - Every other one is answered by the server that drives the phone
12
+ * (CommandInterceptor), because that is where the session's autowait
13
+ * settings, Omni-Vision and network capture live. A hub forwards them.
14
+ * - A name Xenon doesn't have fails with `unknown command`.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.XENON_SCRIPT_NAMES = void 0;
18
+ exports.xenonScriptName = xenonScriptName;
19
+ exports.unknownXenonScriptMessage = unknownXenonScriptMessage;
20
+ /** The names CommandInterceptor and the session-details commands answer. Keep in step with both. */
21
+ exports.XENON_SCRIPT_NAMES = [
22
+ 'setAutowaitProperties',
23
+ 'getAutowaitProperties',
24
+ 'smartTap',
25
+ 'omniClick',
26
+ 'visualTap',
27
+ 'uiInventory',
28
+ 'uiScanExport',
29
+ 'analyzeScreen',
30
+ 'omniScan',
31
+ 'assertVisualState',
32
+ 'addMock',
33
+ 'removeMock',
34
+ 'clearMocks',
35
+ 'getMocks',
36
+ 'getRequests',
37
+ 'exportHar',
38
+ 'setSessionName',
39
+ 'setSessionStatus',
40
+ 'debug',
41
+ 'addTag',
42
+ 'captureEvidence',
43
+ ];
44
+ /** The name in a `xenon: <name>` or `xe: <name>` script, or null for any other script. */
45
+ function xenonScriptName(script) {
46
+ if (typeof script !== 'string')
47
+ return null;
48
+ const match = /^\s*(?:xenon|xe)\s*:\s*(\S+)\s*$/.exec(script);
49
+ return match ? match[1] : null;
50
+ }
51
+ /** The message for a `xenon:` / `xe:` script Xenon doesn't have. */
52
+ function unknownXenonScriptMessage(script) {
53
+ return (`Unknown Xenon command "${script.trim()}". ` +
54
+ `Xenon's execute commands are: ${exports.XENON_SCRIPT_NAMES.join(', ')}.`);
55
+ }
@@ -30,7 +30,7 @@ exports.DefaultPluginArgs = {
30
30
  bindHostOrIp: 'auto',
31
31
  enableDashboard: false,
32
32
  bootedSimulators: false,
33
- healthCheckIntervalMs: 86400000,
33
+ healthCheckIntervalMs: 300000,
34
34
  healthCheckSchedule: undefined,
35
35
  removeDevicesFromDatabaseBeforeRunningThePlugin: false,
36
36
  databaseProvider: undefined,
@@ -50,7 +50,6 @@ exports.DefaultPluginArgs = {
50
50
  recordingCleanupMaxCount: 100,
51
51
  recordingFailedCleanupDays: 2,
52
52
  sessionHeartbeatIntervalMs: 30000,
53
- enableJsonLogging: false,
54
53
  autowait: {
55
54
  enabled: false,
56
55
  timeoutMs: 10000,
@@ -24,7 +24,13 @@ const PluginContext_1 = require("../PluginContext");
24
24
  const hubSessionToken_1 = require("../gateway/hubSessionToken");
25
25
  const localDeviceHosts_1 = require("../device-managers/localDeviceHosts");
26
26
  const SESSION_COOKIE = 'xenon_dashboard_session';
27
- const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
27
+ /** An API key's expiresAt as epoch ms, or undefined for a key that never expires. */
28
+ function keyExpiresAt(expiresAt) {
29
+ if (!expiresAt)
30
+ return undefined;
31
+ const ms = new Date(expiresAt).getTime();
32
+ return Number.isNaN(ms) ? undefined : ms;
33
+ }
28
34
  // Role → scopes derivation. The same table is used in profile token creation
29
35
  // to enforce that members can never grant 'admin'.
30
36
  // Cookie-session scopes: a logged-in user gets the full set of scopes
@@ -185,6 +191,7 @@ function authMiddleware(req, res, next) {
185
191
  apiKeyId: row.id,
186
192
  rateLimit: row.rateLimit,
187
193
  teamIds,
194
+ credentialExpiresAt: keyExpiresAt(row.expiresAt),
188
195
  };
189
196
  req.apiKey = { id: row.id, scopes: row.scopes, rateLimit: row.rateLimit, teamId: (_b = row.teamId) !== null && _b !== void 0 ? _b : null };
190
197
  return next();
@@ -224,6 +231,7 @@ function authMiddleware(req, res, next) {
224
231
  teamId: (_e = payload.teamId) !== null && _e !== void 0 ? _e : null,
225
232
  rateLimit: 300,
226
233
  teamIds,
234
+ credentialExpiresAt: typeof payload.exp === 'number' ? payload.exp * 1000 : undefined,
227
235
  };
228
236
  return next();
229
237
  }
@@ -242,11 +250,14 @@ function authMiddleware(req, res, next) {
242
250
  if (!user || user.status !== 'ACTIVE')
243
251
  return res.status(401).json({ error: 'invalid session' });
244
252
  const isSecure = req.secure || req.headers['x-forwarded-proto'] === 'https';
253
+ // Renewed for as long as the session row now lasts
254
+ // (XENON_USER_SESSION_TTL_MS). A fixed 24 hours here capped any longer
255
+ // setting: the browser dropped the cookie after a day away.
245
256
  res.cookie(SESSION_COOKIE, cookie, {
246
257
  httpOnly: true,
247
258
  secure: isSecure,
248
259
  sameSite: 'strict',
249
- maxAge: SESSION_TTL_MS,
260
+ maxAge: userSessionSvc.ttlMs(),
250
261
  });
251
262
  const teamIds = yield (0, callerTeamIds_1.computeTeamIds)({
252
263
  role: user.role,
@@ -279,7 +290,7 @@ function authMiddleware(req, res, next) {
279
290
  httpOnly: true,
280
291
  secure: isSecure,
281
292
  sameSite: 'strict',
282
- maxAge: SESSION_TTL_MS,
293
+ maxAge: userSessionSvc.ttlMs(),
283
294
  });
284
295
  const teamIds = yield (0, callerTeamIds_1.computeTeamIds)({
285
296
  role: user.role,
@@ -295,6 +306,7 @@ function authMiddleware(req, res, next) {
295
306
  apiKeyId: row.id,
296
307
  rateLimit: row.rateLimit,
297
308
  teamIds,
309
+ credentialExpiresAt: keyExpiresAt(row.expiresAt),
298
310
  };
299
311
  req.apiKey = { id: row.id, scopes: row.scopes, rateLimit: row.rateLimit, teamId: (_g = row.teamId) !== null && _g !== void 0 ? _g : null };
300
312
  return next();
@@ -103,9 +103,9 @@ class CommandCallerVerifier {
103
103
  if (!out)
104
104
  return null;
105
105
  // A Bearer token carries the scopes of the credential that minted it
106
- // (/auth/token), so it is judged like a key. Judging it by role alone,
107
- // as a xenon-session token is for leases, would let an ADMIN's ordinary
108
- // key mint its way to an override.
106
+ // (/auth/token), so it is judged like a key. Judging it by role alone
107
+ // would let an ADMIN's ordinary key mint its way to an override, as a
108
+ // xenon-session token's lease override did through 2.14.
109
109
  return {
110
110
  caller: {
111
111
  userId: out.user.id,
@@ -42,9 +42,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
42
42
  });
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
- exports.ACCEPTED_BEARER_AUDIENCES = void 0;
45
+ exports.SessionTokenSubjectError = exports.ACCEPTED_BEARER_AUDIENCES = void 0;
46
46
  exports.verifyKeyPairCredential = verifyKeyPairCredential;
47
47
  exports.verifyBearerCredential = verifyBearerCredential;
48
+ exports.isWrongSessionToken = isWrongSessionToken;
49
+ exports.verifySessionTokenCredential = verifySessionTokenCredential;
48
50
  const jose = __importStar(require("jose"));
49
51
  const typedi_1 = require("typedi");
50
52
  const ApiKeyService_1 = require("../services/ApiKeyService");
@@ -96,6 +98,12 @@ function verifyKeyPairCredential(accessKey, token) {
96
98
  * JWT) means the token is wrong. Any other error (typically "JWT key service
97
99
  * not initialized") means it could not be checked, and is rethrown once no
98
100
  * audience has verified.
101
+ *
102
+ * The payload's `scopes` are the token's as of now: `admin` is dropped once
103
+ * its user is a MEMBER. A token's scopes are fixed when it is minted, for up
104
+ * to a day, and REST (resolveActor, scopeGuard) and per-command auth's
105
+ * override read them, so through 2.14 an admin demoted since kept admin
106
+ * powers until the token expired.
99
107
  */
100
108
  function verifyBearerCredential(token) {
101
109
  return __awaiter(this, void 0, void 0, function* () {
@@ -119,6 +127,55 @@ function verifyBearerCredential(token) {
119
127
  const user = yield typedi_1.Container.get(UserService_1.UserService).findById(String(payload.sub));
120
128
  if (!user || user.status !== 'ACTIVE')
121
129
  return null;
130
+ return { payload: Object.assign(Object.assign({}, payload), { scopes: scopesForLiveRole(payload.scopes, user.role) }), user };
131
+ });
132
+ }
133
+ /** A token's `scopes` claim without `admin` when its user is now a MEMBER. */
134
+ function scopesForLiveRole(scopes, role) {
135
+ const list = String(scopes !== null && scopes !== void 0 ? scopes : '')
136
+ .split(',')
137
+ .map((s) => s.trim())
138
+ .filter(Boolean);
139
+ if (role === 'ADMIN' || role === 'SUPER_ADMIN')
140
+ return list.join(',');
141
+ return list.filter((s) => s !== 'admin').join(',');
142
+ }
143
+ /** The subject of a session token is no longer an ACTIVE user (deleted or Inactive). */
144
+ class SessionTokenSubjectError extends Error {
145
+ constructor() {
146
+ super('the session token names a user who is deleted or not active');
147
+ this.name = 'SessionTokenSubjectError';
148
+ }
149
+ }
150
+ exports.SessionTokenSubjectError = SessionTokenSubjectError;
151
+ /**
152
+ * Whether verifySessionTokenCredential's error means the token is wrong (a
153
+ * bad signature, audience or lifetime, not a JWT, or a departed user), as
154
+ * opposed to a check that could not run (the signing key or the database
155
+ * unavailable). A wrong token counts as none; a check that could not run
156
+ * refuses the create, as a key whose owner can't be looked up does.
157
+ */
158
+ function isWrongSessionToken(err) {
159
+ return err instanceof jose.errors.JOSEError || err instanceof SessionTokenSubjectError;
160
+ }
161
+ /**
162
+ * A `xenon-session` token (the `xe:options.sessionToken` capability), checked
163
+ * as REST checks a Bearer token: a valid signature, audience and lifetime,
164
+ * and a subject who is an ACTIVE user, looked up now. Through 2.14 session
165
+ * create checked the signature alone, so a deleted or Inactive user's token
166
+ * created sessions as them until it expired.
167
+ *
168
+ * Throws for a token that does not verify, as JwtKeyService.verify does, so
169
+ * the session-token gate and attribution treat a wrong signature and a
170
+ * departed user alike. Whether the token may create sessions at all (its
171
+ * `scopes`) is the caller's to judge.
172
+ */
173
+ function verifySessionTokenCredential(token) {
174
+ return __awaiter(this, void 0, void 0, function* () {
175
+ const payload = yield typedi_1.Container.get(JwtKeyService_1.JwtKeyService).verify(token, { audience: 'xenon-session' });
176
+ const user = yield typedi_1.Container.get(UserService_1.UserService).findById(String(payload.sub));
177
+ if (!user || user.status !== 'ACTIVE')
178
+ throw new SessionTokenSubjectError();
122
179
  return { payload, user: user };
123
180
  });
124
181
  }
package/lib/src/plugin.js CHANGED
@@ -66,22 +66,34 @@ const IPluginArgs_1 = require("./interfaces/IPluginArgs");
66
66
  const NodeDevices_1 = __importDefault(require("./device-managers/NodeDevices"));
67
67
  const appiumUmbrella_1 = require("./sessions/appiumUmbrella");
68
68
  const LiveSessionOwners_1 = require("./services/device-access/LiveSessionOwners");
69
+ const sessionMemory_1 = require("./sessions/sessionMemory");
69
70
  const SessionMetricsService_1 = require("./services/metrics/SessionMetricsService");
71
+ const PhoneNetworkRestore_1 = require("./services/network/PhoneNetworkRestore");
70
72
  const config_1 = require("./config");
71
73
  const SessionManager_1 = require("./sessions/SessionManager");
72
74
  const event_manager_1 = require("./dashboard/event-manager");
73
75
  const asset_manager_1 = require("./dashboard/asset-manager");
74
76
  const session_service_1 = require("./dashboard/services/session-service");
77
+ const shutdownReason_1 = require("./services/session/shutdownReason");
75
78
  const DEVICE_MANAGER_LOCK_NAME = 'DeviceManager';
76
79
  class XenonPlugin extends base_plugin_1.default {
77
80
  constructor(pluginName, cliArgs) {
81
+ var _a;
78
82
  super(pluginName, cliArgs);
79
83
  this.xenonLog = logger_1.default.scope('Plugin');
80
84
  this.pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs);
81
85
  this.aiCommandService = typedi_1.Container.get(AICommandService_1.AICommandService);
82
86
  this.xenonLog.debug(`📱 Plugin Args: ${JSON.stringify(cliArgs)}`);
83
87
  this.pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs, this.cliArgs);
84
- logger_1.XenonLogger.configure({ enableJsonLogging: this.pluginArgs.enableJsonLogging });
88
+ // The option as it was given, not the merged copy above: JSON logging is on
89
+ // when the option says so, or when it isn't given and XENON_JSON_LOGGING does.
90
+ // schema.json gives the option no default, so Appium leaves it out when
91
+ // nobody set it; a default here (the merge above used to add `false`) would
92
+ // overwrite the variable on every start. `configure` ignores undefined.
93
+ const jsonLogging = (_a = this.cliArgs) === null || _a === void 0 ? void 0 : _a.enableJsonLogging;
94
+ logger_1.XenonLogger.configure({
95
+ enableJsonLogging: typeof jsonLogging === 'boolean' ? jsonLogging : undefined,
96
+ });
85
97
  if ((0, networkAddresses_1.shouldAutoResolveBindHost)(this.pluginArgs.bindHostOrIp)) {
86
98
  this.pluginArgs.bindHostOrIp = (0, networkAddresses_1.resolveAdvertisedBindHost)(this.pluginArgs.bindHostOrIp);
87
99
  }
@@ -94,13 +106,18 @@ class XenonPlugin extends base_plugin_1.default {
94
106
  return yield typedi_1.Container.get(CommandInterceptor_1.CommandInterceptor).handle(next, driver, commandName, args, this.pluginArgs, XenonPlugin.IS_HUB);
95
107
  });
96
108
  }
97
- onUnexpectedShutdown(driver, _cause) {
109
+ onUnexpectedShutdown(driver, cause) {
98
110
  return __awaiter(this, void 0, void 0, function* () {
99
111
  var _a;
100
112
  const sessionId = driver.sessionId;
101
113
  typedi_1.Container.get(LiveSessionOwners_1.LiveSessionOwners).forget(sessionId);
114
+ (0, sessionMemory_1.forgetSessionMemory)(sessionId);
102
115
  if (sessionId)
103
116
  yield typedi_1.Container.get(SessionMetricsService_1.SessionMetricsService).stop(sessionId);
117
+ // Appium's new-command timeout ends a session here, not in deleteSession:
118
+ // the phone's network (profile, interceptor proxy) and the capture are
119
+ // put back before the phone is released.
120
+ yield typedi_1.Container.get(PhoneNetworkRestore_1.PhoneNetworkRestore).restoreSession(sessionId, 'driver shut down');
104
121
  const deviceFilter = {
105
122
  session_id: sessionId ? sessionId : undefined,
106
123
  udid: driver.caps && driver.caps.udid ? driver.caps.udid : undefined,
@@ -122,7 +139,9 @@ class XenonPlugin extends base_plugin_1.default {
122
139
  logger_1.default.info(`Unblocking device mapped with filter ${JSON.stringify(deviceFilter)} onUnexpectedShutdown from server`);
123
140
  if (XenonPlugin.IS_HUB && this.pluginArgs.enableDashboard && sessionId) {
124
141
  const sessionLog = this.xenonLog.withSession(sessionId, (_a = driver.caps) === null || _a === void 0 ? void 0 : _a.udid);
125
- sessionLog.info('Unexpected shutdown for session, updating dashboard...');
142
+ // Appium's cause, so an idle session is filed as a timeout.
143
+ const reason = (0, shutdownReason_1.unexpectedShutdownReason)(cause);
144
+ sessionLog.info(`Unexpected shutdown for session (${reason}), updating dashboard...`);
126
145
  const session = SessionManager_1.SESSION_MANAGER.getSession(sessionId);
127
146
  if (session && session.isVideoRecordingInProgress()) {
128
147
  try {
@@ -140,7 +159,7 @@ class XenonPlugin extends base_plugin_1.default {
140
159
  logger_1.default.debug(`[${sessionId}] rescue-video: Failed: ${err.message}`);
141
160
  }
142
161
  }
143
- yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStopped(sessionId, SessionStatus_1.SessionStatus.FAILED, 'Driver shut down unexpectedly');
162
+ yield event_manager_1.DASHBORD_EVENT_MANAGER.onSessionStopped(sessionId, SessionStatus_1.SessionStatus.FAILED, reason);
144
163
  typedi_1.Container.get(TracingService_1.TracingService).endSpan(sessionId, 'ERROR', {
145
164
  'xenon.session.stop_reason': 'Unexpected shutdown',
146
165
  });
package/lib/src/prisma.js CHANGED
@@ -33,7 +33,7 @@ exports.getPrismaClient = getPrismaClient;
33
33
  * can stub individual methods (Prisma's internal Proxy blocks property assignment).
34
34
  */
35
35
  const MODEL_DELEGATES = new Set([
36
- 'build', 'session', 'sessionLog', 'log', 'profiling', 'app', 'device',
36
+ 'build', 'session', 'sessionLog', 'log', 'profiling', 'app', 'device', 'deviceSetting',
37
37
  'pendingSession', 'cLIArgs', 'webhookConfig', 'webConfig', 'locatorEtalon',
38
38
  'lease', 'portLease', 'apiKey', 'selectorState', 'user', 'userSession',
39
39
  'passwordResetToken', 'team', 'teamMember', 'eventLog', 'project', 'recording', 'bookmark',
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.clientProviderFromSchema = clientProviderFromSchema;
40
+ exports.describeDatabaseUrl = describeDatabaseUrl;
41
+ exports.checkDatabase = checkDatabase;
42
+ exports.assertSupportedDatabase = assertSupportedDatabase;
43
+ const fs = __importStar(require("node:fs"));
44
+ const path = __importStar(require("node:path"));
45
+ const logger_1 = __importDefault(require("../logger"));
46
+ const config_1 = require("../config");
47
+ const LABEL = { sqlite: 'SQLite', postgresql: 'PostgreSQL' };
48
+ function providerOfUrl(url) {
49
+ if (url.startsWith('file:'))
50
+ return 'sqlite';
51
+ if (/^postgres(ql)?:\/\//.test(url))
52
+ return 'postgresql';
53
+ return null;
54
+ }
55
+ /** The datasource provider in a Prisma schema's text, or null if it has none. */
56
+ function clientProviderFromSchema(schema) {
57
+ const match = /datasource\s+\w+\s*{[^}]*?provider\s*=\s*"([^"]+)"/.exec(schema);
58
+ return match ? match[1] : null;
59
+ }
60
+ /** A database URL fit for a log line: a SQLite file's path, or a server URL without credentials. */
61
+ function describeDatabaseUrl(url) {
62
+ if (url.startsWith('file:'))
63
+ return url.slice('file:'.length);
64
+ try {
65
+ const parsed = new URL(url);
66
+ return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
67
+ }
68
+ catch (_a) {
69
+ return url.split(':')[0] + ':';
70
+ }
71
+ }
72
+ /**
73
+ * Decide whether the server may start on `url`. Throws an Error whose message
74
+ * says what to change; returns a warning when `configured` disagrees with the
75
+ * client but the URL is usable.
76
+ */
77
+ function checkDatabase(opts) {
78
+ var _a;
79
+ const { configured, url, client } = opts;
80
+ const clientLabel = (_a = LABEL[client]) !== null && _a !== void 0 ? _a : client;
81
+ const urlProvider = providerOfUrl(url);
82
+ const where = describeDatabaseUrl(url);
83
+ if (urlProvider !== client) {
84
+ const kind = urlProvider
85
+ ? `a ${LABEL[urlProvider]} database`
86
+ : 'not a database this install knows';
87
+ const fix = client === 'sqlite'
88
+ ? 'Set DATABASE_URL (or databaseUrl) to a SQLite file, `file:/path/to/xenon.db`, or leave it ' +
89
+ 'unset to use the default under ~/.cache/xenon. The published plugin stores its data in SQLite only.'
90
+ : `Set DATABASE_URL (or databaseUrl) to a ${clientLabel} URL.`;
91
+ throw new Error(`[Database] Cannot start: the database URL (${where}) is ${kind}, but this install's ` +
92
+ `database client is built for ${clientLabel}. ${fix}`);
93
+ }
94
+ if (configured && configured !== client) {
95
+ return {
96
+ warning: `[Database] databaseProvider is "${configured}", but this install's database client is ` +
97
+ `built for ${clientLabel}, so the setting has no effect. Your data is in ${where}. ` +
98
+ `Set databaseProvider to "${client}", or remove it.`,
99
+ };
100
+ }
101
+ return {};
102
+ }
103
+ /** The provider the generated client next to this file was built for. */
104
+ function generatedClientProvider() {
105
+ var _a;
106
+ const schemaPath = path.join(__dirname, '..', 'generated', 'client', 'schema.prisma');
107
+ try {
108
+ return (_a = clientProviderFromSchema(fs.readFileSync(schemaPath, 'utf8'))) !== null && _a !== void 0 ? _a : 'sqlite';
109
+ }
110
+ catch (_b) {
111
+ // The published plugin's client is SQLite; a missing copy means a plain install.
112
+ return 'sqlite';
113
+ }
114
+ }
115
+ /** Startup gate: log the warning, or throw so the server doesn't start on the wrong database. */
116
+ function assertSupportedDatabase() {
117
+ const { warning } = checkDatabase({
118
+ configured: config_1.config.databaseProvider,
119
+ url: config_1.config.databaseUrl,
120
+ client: generatedClientProvider(),
121
+ });
122
+ if (warning)
123
+ logger_1.default.warn(warning);
124
+ }
@@ -49,12 +49,19 @@ let AICommandService = class AICommandService {
49
49
  return __awaiter(this, void 0, void 0, function* () {
50
50
  this.logger.info(`Testing AI locator: ${locator.strategy}=${locator.selector}`);
51
51
  const omniService = typedi_1.Container.get(OmniVisionService_1.OmniVisionService);
52
+ // A session's own route: its test may act on what it found.
53
+ const keep = (found) => {
54
+ if (driver === null || driver === void 0 ? void 0 : driver.sessionId)
55
+ for (const el of found)
56
+ omniService.remember(driver.sessionId, el);
57
+ return found;
58
+ };
52
59
  if (locator.strategy === '-custom:ai-text') {
53
- return yield omniService.findByText(driver, locator.selector);
60
+ return keep(yield omniService.findByText(driver, locator.selector));
54
61
  }
55
62
  else if (locator.strategy === '-custom:ai-icon') {
56
63
  const result = yield omniService.findByIcon(driver, locator.selector);
57
- return result ? [result] : [];
64
+ return keep(result ? [result] : []);
58
65
  }
59
66
  throw new Error(`Unsupported AI strategy: ${locator.strategy}`);
60
67
  });