@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
@@ -1483,13 +1483,20 @@ paths:
1483
1483
  refused, and so is a command that fails, **with `200`** and an `error` field instead of
1484
1484
  `output` (the dashboard's terminal prints either).
1485
1485
 
1486
- - **Android** (`adb shell`): commands starting with `ls`, `ps`, `top`, `dumpsys battery`,
1487
- `dumpsys wifi`, `dumpsys power`, `whoami`, `getprop`, `pm list packages`, `ip addr`, `cat
1488
- /proc/meminfo`, `cat /proc/cpuinfo`, `date`, `uptime` or `netstat`.
1486
+ A command must be one of the commands below, word for word, on one line. Where it takes
1487
+ arguments, they are plain words: letters, digits and `._/:=@%+,-`. Anything a shell would
1488
+ interpret (`;`, `|`, `&`, `$`, backticks, quotes, `<`, `>`) is refused.
1489
1489
 
1490
- - **iOS:** simulator commands `listapps`, `get_app_container`, `list`, `getenv` (through
1491
- `simctl`); go-ios commands `apps`, `info`, `syslog`, `list`, `deviceinfo`, `diagnostics`;
1492
- and `ls`, `ps`, `top`, `whoami`, `date`, `uptime`, `netstat`, `id`.
1490
+ - **Android** (`adb shell`): `ls`, `ps`, `top`, `getprop`, `pm list packages`, `ip addr`,
1491
+ `date` and `netstat`, with arguments; `dumpsys battery`, `dumpsys wifi`, `dumpsys power`,
1492
+ `whoami`, `cat /proc/meminfo`, `cat /proc/cpuinfo` and `uptime`, without.
1493
+
1494
+ - **iOS simulator:** `listapps`, and `get_app_container` and `getenv` with arguments
1495
+ (through `simctl`); `ls`, `ps`, `whoami`, `date`, `uptime` and `id` without arguments
1496
+ (spawned in the simulator).
1497
+
1498
+ - **Real iPhone** (go-ios, through the phone's tunnel): `apps` with arguments and `info`
1499
+ without. Xenon names the phone; a command may not pass `--udid`.
1493
1500
 
1494
1501
  Needs the `MEMBER` role and the `devices` scope. Refused with `409` while another user holds
1495
1502
  the device (their live preview or recording) or runs an Appium session on it; admins are
@@ -1639,14 +1646,14 @@ paths:
1639
1646
  operationId: scanDeviceScreen
1640
1647
  summary: Analyse the screen with OCR and AI
1641
1648
  description: >-
1642
- Screenshots the device, runs OCR over it (every word with its confidence and box) and asks
1643
- the configured AI provider for a qualitative read of the screen. No Appium session is
1644
- needed. Slow: allow tens of seconds.
1649
+ Screenshots the device, runs OCR over it (every word with its confidence and box, in the
1650
+ screenshot's pixels) and sends the screenshot to the configured AI provider for a short
1651
+ description of the screen. No Appium session is needed. Slow: allow tens of seconds.
1645
1652
 
1646
1653
 
1647
1654
  If the analysis itself fails (an empty screenshot, OCR failing), the answer is `500` with
1648
- `{ status: 'error', message }`. If only the AI step fails, the answer is `200` and
1649
- `ai_insights` is `null`.
1655
+ `{ status: 'error', message }`. If only the AI step fails, or no AI provider is
1656
+ configured, the answer is `200`, `ai_insights` is `null` and `ai_insights_error` says why.
1650
1657
 
1651
1658
 
1652
1659
  Open to watchers. Needs the `MEMBER` role; any scope will do.
@@ -1683,10 +1690,27 @@ paths:
1683
1690
  y0: 210
1684
1691
  x1: 188
1685
1692
  y1: 236
1686
- ai_insights:
1687
- summary: >-
1688
- A sign-in form with email and password fields and a disabled Sign in
1689
- button.
1693
+ ai_insights: >-
1694
+ A sign-in screen with empty email and password fields and a disabled Sign
1695
+ in button. No error or dialog is shown.
1696
+ noProvider:
1697
+ summary: No AI provider configured
1698
+ value:
1699
+ status: success
1700
+ value:
1701
+ timestamp: '2026-10-04T14:05:22.118Z'
1702
+ ocr:
1703
+ text: Sign in
1704
+ words:
1705
+ - text: Sign
1706
+ confidence: 96.2
1707
+ bbox:
1708
+ x0: 142
1709
+ y0: 210
1710
+ x1: 188
1711
+ y1: 236
1712
+ ai_insights: null
1713
+ ai_insights_error: No AI provider is configured
1690
1714
  '400':
1691
1715
  description: This server has no device manager for the platform, or the udid is malformed.
1692
1716
  content:
@@ -1745,8 +1769,9 @@ paths:
1745
1769
  Runs one of Xenon's AI locator strategies against a fresh screenshot, as a test would with
1746
1770
  `findElement`, without an Appium session.
1747
1771
 
1748
- - `-custom:ai-text`: OCR; every word containing `selector` (case-insensitive, confidence
1749
- above 60%).
1772
+ - `-custom:ai-text`: OCR; every place `selector` appears (case-insensitive), within a word
1773
+ or across neighbouring words on one line, so `Sign in` matches; each match read with
1774
+ confidence above 60%.
1750
1775
 
1751
1776
  - `-custom:ai-icon`: the configured AI provider looks for what `selector` describes; at most
1752
1777
  one match, a 40×40 box around the point it names.
@@ -1939,6 +1964,30 @@ paths:
1939
1964
  On a hub, for a node's phone, the request is forwarded to the node, which holds the phone by
1940
1965
  its own rules; on success the hub also marks the phone busy and held by you at once. A cloud
1941
1966
  provider's phone answers `501`.
1967
+
1968
+ One capture per Android device where it can be. A page that shows MJPEG (a browser that
1969
+ cannot decode H.264, which on plain http it cannot, or a page whose H.264 player failed)
1970
+ sends `player: mjpeg`: the server then starts the MJPEG capture, and ends an H.264 capture
1971
+ running for the device once nobody plays it: first, if nobody does, else when its last
1972
+ viewer leaves. Another page may still be playing it, and keeps it until then. A node on
1973
+ 2.13 or older ignores `player`.
1974
+ requestBody:
1975
+ required: false
1976
+ content:
1977
+ application/json:
1978
+ schema:
1979
+ type: object
1980
+ properties:
1981
+ player:
1982
+ type: string
1983
+ enum:
1984
+ - mjpeg
1985
+ description: >-
1986
+ Set to `mjpeg` when the page shows MJPEG. Left out, or any other value, the
1987
+ server chooses: H.264 for an Android device when the setting is on and the
1988
+ device is not being recorded, MJPEG otherwise.
1989
+ example:
1990
+ player: mjpeg
1942
1991
  responses:
1943
1992
  '200':
1944
1993
  description: The preview is running. `type` says which transport to use.
@@ -2968,10 +3017,14 @@ components:
2968
3017
  y1:
2969
3018
  type: number
2970
3019
  ai_insights:
2971
- type: object
3020
+ type: string
2972
3021
  nullable: true
2973
- additionalProperties: true
2974
- description: The AI provider's analysis; null if it was skipped or failed.
3022
+ description: >-
3023
+ The AI provider's description of the screen, in plain text; null when there is no
3024
+ AI provider or its call failed.
3025
+ ai_insights_error:
3026
+ type: string
3027
+ description: Why `ai_insights` is null. Present only then.
2975
3028
  ControlTestLocatorRequest:
2976
3029
  type: object
2977
3030
  required:
@@ -16,7 +16,11 @@ paths:
16
16
  `session_id` is that value, as an object, or an empty `200` body
17
17
  when there is none. When the Appium dashboard plugin is installed,
18
18
  each row in the list also gets `dashboard_link` and
19
- `total_session_count`.
19
+ `total_session_count`. Xenon reaches the plugin on this server's own
20
+ address and port, and the link is `<XENON_PUBLIC_URL>/dashboard?…`,
21
+ or `/dashboard?…` on the server the caller has open when
22
+ `XENON_PUBLIC_URL` isn't set. Through 2.14 both were built from the
23
+ `Host` header of the first request after startup, signed in or not.
20
24
 
21
25
  `GET /api/device` is the same endpoint.
22
26
  tags:
@@ -141,7 +145,8 @@ paths:
141
145
  description: |
142
146
  Sets `userBlocked` on a device, so no new session or lease is
143
147
  allocated to it. A session already running on it is not affected,
144
- and neither is `busy`.
148
+ and neither is `busy`. The device stays blocked when it disconnects
149
+ and comes back, through restarts too.
145
150
 
146
151
  Send both `udid` and `host`, exactly as `GET /api/devices` shows
147
152
  them: the device is the row of that udid on that host. Without
@@ -247,7 +252,9 @@ paths:
247
252
  Replaces the device's tag list with `tags` (it doesn't append). Tags
248
253
  are free-form labels, such as `flaky` or `lab-row-3`, that a lease
249
254
  can filter on. The row is chosen by exact `udid` and `host`; a pair
250
- that names no device changes nothing and still answers `200`.
255
+ that names no device is `404` (through 2.13 it changed nothing and
256
+ answered `200`). The device keeps its tags when it disconnects and
257
+ comes back, through restarts too.
251
258
 
252
259
  Needs role `ADMIN` and the `devices` scope.
253
260
  tags:
@@ -298,6 +305,14 @@ paths:
298
305
  $ref: '#/components/responses/Unauthorized'
299
306
  '403':
300
307
  $ref: '#/components/responses/Forbidden'
308
+ '404':
309
+ description: No device of that udid on that host.
310
+ content:
311
+ application/json:
312
+ schema:
313
+ $ref: '#/components/schemas/Error'
314
+ example:
315
+ error: Device not found
301
316
  '429':
302
317
  $ref: '#/components/responses/RateLimited'
303
318
  /api/device/{udid}/team:
@@ -307,10 +322,14 @@ paths:
307
322
  description: |
308
323
  Gives the device to a team, or with `teamId: null` returns it to the
309
324
  shared pool, which every member sees. A missing `teamId` counts as
310
- null. Every row with this udid changes (on a hub, each node that
325
+ null. Every device with this udid changes (on a hub, each node that
311
326
  reports it), and allocation and live dashboard events follow the new
312
327
  team at once.
313
328
 
329
+ A device keeps its team when it disconnects and comes back, through
330
+ restarts too, and this also moves one of this udid that isn't
331
+ connected now: a team that one belongs to can't be deleted.
332
+
314
333
  Needs role `ADMIN` and the `admin` scope.
315
334
  tags:
316
335
  - Devices
@@ -336,7 +355,7 @@ paths:
336
355
  example: clx9k2v0a0000qz8r4h7m3n2p
337
356
  responses:
338
357
  '200':
339
- description: The device's team changed. `updated` is how many rows changed.
358
+ description: The device's team changed. `updated` is how many devices changed, connected or not.
340
359
  content:
341
360
  application/json:
342
361
  schema:
@@ -432,7 +451,10 @@ paths:
432
451
  (`reservedByUserId`), who alone, or an admin, may release or extend
433
452
  it. A device already reserved by someone else, or running an Appium
434
453
  session, is refused with `409`. The same user under the same
435
- `reservedBy` may reserve it again, which restarts the clock.
454
+ `reservedBy` may reserve it again, which restarts the clock. The
455
+ reservation lasts until it ends even if the device disconnects or
456
+ the server restarts meanwhile; one that ended while the device was
457
+ away is not brought back.
436
458
 
437
459
  The device is the row of `udid` on `host`, so send both exactly as
438
460
  `GET /api/devices` shows them. No such row, or a device outside the
@@ -2047,7 +2069,9 @@ components:
2047
2069
  description: On a hub, the preview hold the node last reported (`manual_<userId>_<udid>`).
2048
2070
  dashboard_link:
2049
2071
  type: string
2050
- description: Only with the Appium dashboard plugin installed.
2072
+ description: >-
2073
+ Only with the Appium dashboard plugin installed: the plugin's page for this device,
2074
+ under `XENON_PUBLIC_URL`, or a path on this server when it isn't set.
2051
2075
  total_session_count:
2052
2076
  type: integer
2053
2077
  description: Only with the Appium dashboard plugin installed.
@@ -10,9 +10,10 @@ paths:
10
10
  Public. Tells the sign-in page how a user who forgot their password can get back in, before
11
11
  anyone is signed in.
12
12
 
13
- `passwordReset` is `email` when the server has SMTP configured (the forgot-password form
14
- really emails a link), otherwise `admin` (the user must ask an administrator, who issues a
15
- link with `POST /api/users/{id}/reset-link`). Nothing else about the mail setup is revealed.
13
+ `passwordReset` is `email` when the server has SMTP configured and `XENON_PUBLIC_URL` set to
14
+ its address (the forgot-password form really emails a link, which points there),
15
+ otherwise `admin` (the user must ask an administrator, who issues a link with
16
+ `POST /api/users/{id}/reset-link`). Nothing else about the mail setup is revealed.
16
17
  tags:
17
18
  - Authentication
18
19
  security: []
@@ -34,8 +35,8 @@ paths:
34
35
  Public. Checks the email (case-insensitive) and password of an `ACTIVE` user and, on success,
35
36
  sets the `xenon_dashboard_session` cookie (HttpOnly, `SameSite=Strict`, `Secure` when the
36
37
  request came over HTTPS or with `X-Forwarded-Proto: https`). The cookie lives for the
37
- server's user-session lifetime (24 hours by default, `XENON_USER_SESSION_TTL_MS`) and is
38
- renewed on every authenticated request. Updates the user's last sign-in time.
38
+ server's user-session lifetime (24 hours by default, `XENON_USER_SESSION_TTL_MS`, which may be
39
+ longer or shorter) and is renewed for that long on every authenticated request. Updates the user's last sign-in time.
39
40
 
40
41
  A wrong password, an unknown email and an inactive user all answer the same `401`.
41
42
 
@@ -116,8 +117,11 @@ paths:
116
117
  addresses have accounts. Then, in the background, if the email belongs to an `ACTIVE` user and
117
118
  the server can deliver mail (SMTP configured, or the opt-in log fallback
118
119
  `XENON_PASSWORD_RESET_LOG_FALLBACK=true`), it creates a single-use reset token (valid 1 hour by
119
- default) and sends a link to `/xenon/reset-password#<token>`. Without either, no token is created;
120
- `GET /api/auth/options` says `admin` in that case.
120
+ default, `XENON_RESET_TOKEN_TTL_MS`) and sends a link to
121
+ `<server address>/xenon/reset-password#<token>`. The server address is `XENON_PUBLIC_URL`
122
+ (such as `https://xenon.example.com`; a trailing `/xenon/` is dropped), never the request's
123
+ `Host`, which whoever asks chooses. Without a way to deliver mail, or without a usable
124
+ `XENON_PUBLIC_URL`, no token is created; `GET /api/auth/options` says `admin` then.
121
125
 
122
126
  **Rate limit.** 3 requests per 15 minutes per client IP by default, then `429` with
123
127
  `Retry-After`.
@@ -387,16 +391,28 @@ paths:
387
391
  (`XENON_MCP_TOKEN_TTL_SEC`). It carries granular `scopes` (by default `appium:use` and
388
392
  `xenon:devices:read`, as far as the caller's scopes allow) and, as a REST bearer token, only
389
393
  the flat scopes those need (`appium:use` gives `sessions`, `xenon:devices:lock` gives
390
- `devices`). The answer also has a `sessionToken` (audience `xenon-session`, same lifetime)
391
- to put in an Appium session's `xe:options.sessionToken`.
394
+ `devices`). When the grant includes `appium:use` the answer also has a `sessionToken`
395
+ (audience `xenon-session`, same lifetime) to put in an Appium session's
396
+ `xe:options.sessionToken`. It carries the `sessions` scope, and `admin` only when the
397
+ credential minting it has `admin` and the grant is the default one (no `scopes` asked for) or
398
+ a full-admin one. With `admin` a session may use a lease someone else made, and only while
399
+ its user is still an `ADMIN` (a `SUPER_ADMIN`'s session may, `admin` or not). A credential
400
+ without `sessions` or `admin`, a read-only one for instance, gets no `sessionToken`.
401
+
402
+ **Never longer than the credential.** A token minted with a Bearer token, or with an API key
403
+ that expires, ends no later than that credential: `expiresIn` is cut to what is left of it. A
404
+ credential with no time left answers `400`. A dashboard sign-in renews itself, so its tokens get
405
+ the full lifetime.
406
+
407
+ **Scopes follow the role.** A token's scopes are fixed when it is minted, but its `admin` scope
408
+ stops counting once its user is a `MEMBER`: the API and per-command auth read the token's scopes
409
+ without it.
392
410
 
393
411
  Granular scopes a credential may grant: `read` gives `xenon:devices:read` and
394
412
  `xenon:analytics:read`; `sessions` gives `appium:use` and `xenon:recordings`; `devices` gives
395
413
  `xenon:devices:read`, `xenon:devices:lock` and `xenon:recordings`; `admin` gives all of them.
396
414
 
397
- Cookie callers need a same-host `Origin` or `Referer`. So, as the server is written, does a
398
- caller using only `Authorization: Bearer`: the same-origin check exempts only requests with an
399
- `x-xenon-access-key` header.
415
+ Cookie callers need a same-host `Origin` or `Referer`.
400
416
  tags:
401
417
  - Authentication
402
418
  requestBody:
@@ -441,7 +457,9 @@ paths:
441
457
  - xenon:devices:read
442
458
  sessionToken: eyJhbGciOiJSUzI1NiIsImtpZCI6IjNmMmE5YzRlN2QxYiJ9.eyJzdWIiOiI3YzFlMmQ0YSIsImF1ZCI6Inhlbm9uLXNlc3Npb24ifQ.c2lnbmF0dXJl
443
459
  '400':
444
- description: An unsupported audience, `scopes` that isn't an array, or an unknown granular scope.
460
+ description: |
461
+ An unsupported audience, `scopes` that isn't an array, an unknown granular scope, or a
462
+ credential with no time left to give the token.
445
463
  content:
446
464
  application/json:
447
465
  schema:
@@ -451,6 +469,10 @@ paths:
451
469
  value:
452
470
  error: bad_request
453
471
  details: 'unsupported audience: xenon-node'
472
+ expired:
473
+ value:
474
+ error: bad_request
475
+ details: the credential used to mint this token has expired
454
476
  unknownScope:
455
477
  value:
456
478
  error: unknown_scope
@@ -584,8 +606,12 @@ paths:
584
606
  description: |
585
607
  Gives the caller a new access key. The old one stops working at once, for every one of their
586
608
  API tokens: scripts and CI using the key pair must be updated. The tokens themselves are kept.
587
- Any signed-in user may call it; with authentication disabled it rotates the first
588
- `SUPER_ADMIN`'s key. Cookie callers need a same-host `Origin` or `Referer`.
609
+
610
+ Since that cuts off every key pair the user has, whatever its scopes, it takes a dashboard
611
+ sign-in (the cookie) or a credential with the `admin` scope. Any other API token or Bearer
612
+ token, a `read`-only one for instance, gets `403`. Through 2.14 any credential could. With
613
+ authentication disabled it rotates the first `SUPER_ADMIN`'s key. Cookie callers need a
614
+ same-host `Origin` or `Referer`.
589
615
  tags:
590
616
  - Profile
591
617
  responses:
@@ -600,7 +626,13 @@ paths:
600
626
  '401':
601
627
  $ref: '#/components/responses/Unauthorized'
602
628
  '403':
603
- $ref: '#/components/responses/Forbidden'
629
+ description: A credential other than a dashboard sign-in, without the `admin` scope.
630
+ content:
631
+ application/json:
632
+ schema:
633
+ $ref: '#/components/schemas/Error'
634
+ example:
635
+ error: rotating the access key needs a dashboard sign-in or a credential with the admin scope
604
636
  '404':
605
637
  $ref: '#/components/responses/IdentityUserNotFound'
606
638
  '429':
@@ -653,6 +685,11 @@ paths:
653
685
  role was checked, so an admin's `read`-only key could mint a wider token. The token has the
654
686
  default rate limit (300 a minute) and no team binding.
655
687
 
688
+ Made with a credential that expires (a Bearer token, or an API key with an `expiresAt`), the
689
+ token expires no later than it: without `expiresAt` it gets that credential's, and a later
690
+ `expiresAt` answers `400`. Through 2.14 a 1-hour Bearer token could make a token that never
691
+ expired. From the dashboard, `expiresAt` may be left out for a token that never expires.
692
+
656
693
  Any signed-in user may call it. Cookie callers need a same-host `Origin` or `Referer`.
657
694
  tags:
658
695
  - Profile
@@ -681,8 +718,8 @@ paths:
681
718
  expiresAt: '2027-01-01T00:00:00.000Z'
682
719
  '400':
683
720
  description: >-
684
- `name` is missing, `expiresAt` is invalid or past, a scope is unknown, or a scope is
685
- beyond your role or the credential you are using.
721
+ `name` is missing, `expiresAt` is invalid, past or later than the credential you are
722
+ using, a scope is unknown, or a scope is beyond your role or the credential you are using.
686
723
  content:
687
724
  application/json:
688
725
  schema:
@@ -694,6 +731,11 @@ paths:
694
731
  expiry:
695
732
  value:
696
733
  error: expiresAt must be in the future
734
+ outlives:
735
+ value:
736
+ error: >-
737
+ expiresAt can be no later than the credential you are using, which expires at
738
+ 2026-10-04T13:00:00.000Z
697
739
  unknown:
698
740
  value:
699
741
  error: scopes must be some of read, sessions, devices, admin
@@ -872,7 +914,9 @@ paths:
872
914
  - You can't change your own role.
873
915
  - The last active `SUPER_ADMIN` can't be demoted or deactivated.
874
916
  - Setting `status` to `INACTIVE` signs the user out of every dashboard session. Their API
875
- tokens and bearer tokens stop working too, since an inactive user's credentials are refused.
917
+ tokens, bearer tokens and session tokens stop working too, since an inactive user's
918
+ credentials are refused: on this API at once, for a new Appium session at once, and for the
919
+ commands of a session already running within 30 seconds with `XENON_REQUIRE_COMMAND_AUTH`.
876
920
 
877
921
  Cookie callers need a same-host `Origin` or `Referer`.
878
922
  tags:
@@ -973,9 +1017,11 @@ paths:
973
1017
  Needs role `ADMIN` or above. An `ADMIN` may issue links only for `MEMBER` accounts. Creates a
974
1018
  single-use reset link for an `ACTIVE` user, valid 1 hour by default.
975
1019
 
976
- With SMTP configured the link is emailed to the user and the answer is `emailed: true`.
977
- Without it the link is returned once, in this answer only (`Cache-Control: no-store`), for the
978
- admin to pass on; it is never written to the server log. Your own password goes through
1020
+ With SMTP and `XENON_PUBLIC_URL` configured the link is emailed to the user and the answer is
1021
+ `emailed: true`; the emailed link points at `XENON_PUBLIC_URL`, never at the request's `Host`.
1022
+ Without either the link is returned once, in this answer only (`Cache-Control: no-store`), for
1023
+ the admin to pass on; it is never written to the server log. A returned link points at
1024
+ `XENON_PUBLIC_URL` when it is set, else at the address the admin reached the server on. Your own password goes through
979
1025
  `POST /api/auth/change-password` instead. Cookie callers need a same-host `Origin` or
980
1026
  `Referer`.
981
1027
  tags:
@@ -1322,10 +1368,20 @@ paths:
1322
1368
  summary: Create an API key
1323
1369
  description: |
1324
1370
  Creates an API token owned by the caller and returns its value **once** as `key`; it is used as
1325
- `x-xenon-token` with the caller's access key. Unlike `POST /api/profile/tokens`, it can bind the
1326
- key to a team (the key then sees that team's devices only, within its owner's teams), set its
1327
- rate limit and grant any scope, `admin` included. Needs role `ADMIN` or above and the `admin`
1328
- scope. Cookie callers need a same-host `Origin` or `Referer`.
1371
+ `x-xenon-token` with the caller's access key. Unlike `POST /api/profile/tokens`, it can name a
1372
+ team, set its rate limit and grant any scope, `admin` included. `scopes` must be some of `read`,
1373
+ `sessions`, `devices` and `admin`; any other string answers `400` (through 2.14 it was stored,
1374
+ and gave the key nothing). Needs role `ADMIN` or above and the `admin` scope. Cookie callers
1375
+ need a same-host `Origin` or `Referer`.
1376
+
1377
+ **The team doesn't narrow the key.** The key belongs to the caller, an administrator, and an
1378
+ administrator's credentials reach every team's devices, so the key does too whichever team it
1379
+ names. The team is recorded on the leases the key takes, and limits the key only if its owner
1380
+ is later made a `MEMBER`. A test session keeps to one team with `xe:options.team`.
1381
+
1382
+ Made with a credential that expires (a Bearer token, or an API key with an `expiresAt`), the
1383
+ key expires no later than it: without `expiresAt` it gets that credential's, and a later
1384
+ `expiresAt` answers `400`.
1329
1385
  tags:
1330
1386
  - API Keys
1331
1387
  requestBody:
@@ -1365,7 +1421,9 @@ paths:
1365
1421
  key: 5d2e8f1a9c4b7e3d6a0f2c8b1e5d9a4c7f3b6e0d2a8c5f1e9b4d7a3c6f0e2b8d
1366
1422
  expiresAt: '2027-04-01T00:00:00.000Z'
1367
1423
  '400':
1368
- description: '`name` or `scopes` is missing, or `expiresAt` is invalid or past.'
1424
+ description: >-
1425
+ `name` or `scopes` is missing, a scope is unknown, or `expiresAt` is invalid, past or later
1426
+ than the credential making the request.
1369
1427
  content:
1370
1428
  application/json:
1371
1429
  schema:
@@ -1374,6 +1432,9 @@ paths:
1374
1432
  missing:
1375
1433
  value:
1376
1434
  error: name and scopes required
1435
+ unknown:
1436
+ value:
1437
+ error: scopes must be some of read, sessions, devices, admin
1377
1438
  expiry:
1378
1439
  value:
1379
1440
  error: expiresAt must be an ISO-8601 datetime
@@ -1835,7 +1896,9 @@ components:
1835
1896
  type: string
1836
1897
  sessionToken:
1837
1898
  type: string
1838
- description: '`xenon-mcp` only: a session token for `xe:options.sessionToken`, with the same lifetime.'
1899
+ description: >-
1900
+ `xenon-mcp` only, and only when the grant includes `appium:use`: a session token for
1901
+ `xe:options.sessionToken`, with the same lifetime.
1839
1902
  IdentityAccessKey:
1840
1903
  type: object
1841
1904
  properties:
@@ -1881,7 +1944,10 @@ components:
1881
1944
  type: string
1882
1945
  format: date-time
1883
1946
  nullable: true
1884
- description: When the token stops working; must be in the future. Null or absent for never.
1947
+ description: >-
1948
+ When the token stops working; must be in the future, and no later than the credential
1949
+ making the request when that one expires. Null or absent for never, or, with a credential
1950
+ that expires, for when that credential does.
1885
1951
  IdentityCreatedToken:
1886
1952
  type: object
1887
1953
  properties:
@@ -2099,12 +2165,18 @@ components:
2099
2165
  teamId:
2100
2166
  type: string
2101
2167
  nullable: true
2102
- description: Bind the key to this team.
2168
+ description: >-
2169
+ A team to note on the key. The key belongs to the caller, an administrator, so like them it
2170
+ reaches every team's devices whichever team this names: the team is recorded on the leases
2171
+ the key takes, and limits the key only if its owner is later made a `MEMBER`.
2103
2172
  expiresAt:
2104
2173
  type: string
2105
2174
  format: date-time
2106
2175
  nullable: true
2107
- description: When the key stops working; must be in the future. Null or absent for never.
2176
+ description: >-
2177
+ When the key stops working; must be in the future, and no later than the credential
2178
+ making the request when that one expires. Null or absent for never, or, with a credential
2179
+ that expires, for when that credential does.
2108
2180
  IdentityProject:
2109
2181
  type: object
2110
2182
  properties: