@xenon-device-management/xenon 2.14.0 → 2.16.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 (335) hide show
  1. package/README.md +44 -29
  2. package/lib/package.json +6 -5
  3. package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-GmckX_7r.js} +1 -1
  5. package/lib/public/assets/DeviceMosaicView-DqODzkyo.js +2 -0
  6. package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-7hETKEWb.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-GOgssFEP.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-Yvf6RGMd.js} +1 -1
  9. package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-KrPnfdsf.js} +1 -1
  10. package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-C6WjZFeI.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CSRIhZEh.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-CB8ST3nE.js} +1 -1
  13. package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C9o90KId.js} +1 -1
  14. package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-CPm3w9l2.js} +1 -1
  15. package/lib/public/assets/{Table-BcTm5xV2.js → Table-DwIgERRt.js} +1 -1
  16. package/lib/public/assets/{activity-DOVdsfMQ.js → activity-DrLbHifn.js} +1 -1
  17. package/lib/public/assets/ai-settings-DU_Ls06P.js +16 -0
  18. package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-CWYdEzJ_.js} +1 -1
  19. package/lib/public/assets/{apps-D0SC6uw6.js → apps-BqNVdln7.js} +1 -1
  20. package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BtB0_Jg_.js} +1 -1
  21. package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-C8NhdtyN.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-gMg0ffvv.js} +1 -1
  23. package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CiYZ3N4M.js} +2 -2
  24. package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-EaaqDmLN.js} +1 -1
  25. package/lib/public/assets/{button-Dda4irn_.js → button-DDau4Axv.js} +1 -1
  26. package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-DQ83B1ed.js} +1 -1
  27. package/lib/public/assets/{check-CZ9iw9-e.js → check-K1ovTQam.js} +1 -1
  28. package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-Biq00pMt.js} +1 -1
  29. package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CBv88cH7.js} +1 -1
  30. package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-Bmltailn.js} +1 -1
  31. package/lib/public/assets/{clock-CpuDKysU.js → clock-BPrld-3h.js} +1 -1
  32. package/lib/public/assets/{copy-CAQydNFe.js → copy-t0QB14JI.js} +1 -1
  33. package/lib/public/assets/derive-UnLRp4r3.js +1 -0
  34. package/lib/public/assets/device-explorer-BgGZ8AvV.js +282 -0
  35. package/lib/public/assets/{download-C9XG6lLv.js → download-BroHxNOh.js} +1 -1
  36. package/lib/public/assets/forgot-password-Bjmf49i6.js +1 -0
  37. package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-DeD__6wa.js} +6 -6
  38. package/lib/public/assets/{index-DJGc1M2f.js → index-72oF89LJ.js} +1 -1
  39. package/lib/public/assets/index-B0JeuZbg.js +15 -0
  40. package/lib/public/assets/index-B0gQkEg0.js +219 -0
  41. package/lib/public/assets/{input-Cm0tmSoc.js → input-DIc4MSC7.js} +1 -1
  42. package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-BcWm2vW8.js} +1 -1
  43. package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-B06IJJad.js} +1 -1
  44. package/lib/public/assets/{lock-CCJv9m_I.js → lock-pVLvnN30.js} +1 -1
  45. package/lib/public/assets/{login-f8vOVW-U.js → login-CN77vy--.js} +1 -1
  46. package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-CSdzQSkY.js} +1 -1
  47. package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-BtrN5IkI.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
  49. package/lib/public/assets/{network-ebT86qoi.js → network-BSfA8ZxG.js} +1 -1
  50. package/lib/public/assets/{overview-C0aykVrd.js → overview-BfTebCK5.js} +1 -1
  51. package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-D8KUX6Qg.js} +1 -1
  52. package/lib/public/assets/{play-DLG9t8FU.js → play-Cvc8xzic.js} +1 -1
  53. package/lib/public/assets/{plus-nBFSMu_X.js → plus-C9Zj84UF.js} +1 -1
  54. package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-Bi27STgs.js} +1 -1
  55. package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-CPsOuwSu.js} +1 -1
  56. package/lib/public/assets/runbook-page-DEQ-DBOX.js +304 -0
  57. package/lib/public/assets/{select-88jHcl-Z.js → select-BWpnhWXj.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BefSCSpe.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-MSk96Zz7.js} +1 -1
  60. package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
  61. package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
  62. package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-V6x-cXwy.js} +1 -1
  63. package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-Duex-GZD.js} +1 -1
  64. package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BBcoqBmL.js} +1 -1
  65. package/lib/public/assets/{teams-B4-jrr-1.js → teams-ts8p5rEh.js} +1 -1
  66. package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-CZ2ew6AZ.js} +1 -1
  67. package/lib/public/assets/{upload-BG7_rMXK.js → upload-Dv99y_le.js} +1 -1
  68. package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-DrYC8JCF.js} +1 -1
  69. package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
  70. package/lib/public/assets/{users-CFba5TSy.js → users-D7tKMESH.js} +1 -1
  71. package/lib/public/assets/{users-Cp0beJm3.js → users-gJ5Y01yo.js} +1 -1
  72. package/lib/public/assets/{video-off-DDsEISjV.js → video-off-CqZVuZGs.js} +1 -1
  73. package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-DI5DSW99.js} +1 -1
  74. package/lib/public/assets/{zap-l4VaXzq8.js → zap-ClcHFxfo.js} +1 -1
  75. package/lib/public/index.html +1 -1
  76. package/lib/schema.json +16 -12
  77. package/lib/src/InternalHttpClient.js +27 -5
  78. package/lib/src/XenonCapabilityManager.js +10 -3
  79. package/lib/src/app/dashboardPluginLink.js +117 -0
  80. package/lib/src/app/index.js +13 -34
  81. package/lib/src/app/openapi/control.yaml +6 -4
  82. package/lib/src/app/openapi/grid.yaml +8 -2
  83. package/lib/src/app/openapi/identity.yaml +103 -31
  84. package/lib/src/app/openapi/platform.yaml +33 -15
  85. package/lib/src/app/openapi/sessions.yaml +12 -3
  86. package/lib/src/app/routers/apikeys.js +14 -2
  87. package/lib/src/app/routers/auth.js +31 -6
  88. package/lib/src/app/routers/control.js +13 -5
  89. package/lib/src/app/routers/dashboard.js +42 -27
  90. package/lib/src/app/routers/grid.js +5 -3
  91. package/lib/src/app/routers/profile.js +21 -0
  92. package/lib/src/app/routers/users.js +12 -6
  93. package/lib/src/app/swagger.js +2 -0
  94. package/lib/src/app/ws/h264StreamWs.js +10 -1
  95. package/lib/src/app/ws/nodeSocketRelay.js +77 -11
  96. package/lib/src/config.js +1 -0
  97. package/lib/src/dashboard/commandLogFields.js +42 -0
  98. package/lib/src/dashboard/commands.js +5 -5
  99. package/lib/src/dashboard/event-manager.js +64 -204
  100. package/lib/src/dashboard/services/failure-analysis-service.js +118 -105
  101. package/lib/src/dashboard/services/failureCategories.js +264 -0
  102. package/lib/src/dashboard/sessionCommandSummary.js +39 -0
  103. package/lib/src/data-service/device-store.js +14 -1
  104. package/lib/src/data-service/prisma-store.js +19 -7
  105. package/lib/src/data-service/web-config-service.js +7 -0
  106. package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
  107. package/lib/src/device-managers/IOSDeviceManager.js +10 -0
  108. package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
  109. package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
  110. package/lib/src/device-utils.js +29 -3
  111. package/lib/src/gateway/defaultGateway.js +1 -1
  112. package/lib/src/gateway/forwardToNode.js +6 -11
  113. package/lib/src/gateway/healReport.js +87 -0
  114. package/lib/src/gateway/hubSessionToken.js +27 -4
  115. package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
  116. package/lib/src/gateway/sessionGateway.js +12 -2
  117. package/lib/src/generated/client/edge.js +4 -3
  118. package/lib/src/generated/client/index-browser.js +1 -0
  119. package/lib/src/generated/client/index.d.ts +32 -0
  120. package/lib/src/generated/client/index.js +4 -3
  121. package/lib/src/generated/client/package.json +1 -1
  122. package/lib/src/generated/client/schema.prisma +3 -0
  123. package/lib/src/generated/client/wasm.js +1 -0
  124. package/lib/src/helpers/outboundProxy.js +219 -0
  125. package/lib/src/index.js +21 -0
  126. package/lib/src/interceptors/CommandInterceptor.js +256 -196
  127. package/lib/src/interceptors/xenonScripts.js +28 -7
  128. package/lib/src/logger.js +3 -1
  129. package/lib/src/middleware/authMiddleware.js +15 -3
  130. package/lib/src/middleware/commandCaller.js +3 -3
  131. package/lib/src/middleware/verifyCredential.js +58 -1
  132. package/lib/src/plugin.js +16 -6
  133. package/lib/src/scripts/database-check.js +3 -3
  134. package/lib/src/scripts/initialize-database.js +35 -33
  135. package/lib/src/scripts/run-migrations.js +1079 -27
  136. package/lib/src/services/AICommandService.js +9 -2
  137. package/lib/src/services/AIService.js +192 -42
  138. package/lib/src/services/ApiKeyService.js +16 -5
  139. package/lib/src/services/EmailService.js +29 -0
  140. package/lib/src/services/InterceptorService.js +6 -2
  141. package/lib/src/services/OrphanSweeper.js +8 -0
  142. package/lib/src/services/SelectorStateService.js +11 -7
  143. package/lib/src/services/SelectorVerificationJob.js +21 -6
  144. package/lib/src/services/ServerManager.js +11 -1
  145. package/lib/src/services/SessionLifecycleService.js +118 -44
  146. package/lib/src/services/SocketClient.js +11 -0
  147. package/lib/src/services/SocketServer.js +380 -45
  148. package/lib/src/services/TeamService.js +5 -1
  149. package/lib/src/services/TracingService.js +171 -60
  150. package/lib/src/services/UserService.js +14 -3
  151. package/lib/src/services/UserSessionService.js +16 -4
  152. package/lib/src/services/bug-report/video-slice.js +4 -1
  153. package/lib/src/services/device-access/leaseOverride.js +7 -5
  154. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
  155. package/lib/src/services/healing/HealEtalonService.js +3 -2
  156. package/lib/src/services/healing/HealingOrchestrator.js +57 -72
  157. package/lib/src/services/healing/OcrHealingProvider.js +41 -64
  158. package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
  159. package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
  160. package/lib/src/services/healing/fingerprintIdentity.js +126 -0
  161. package/lib/src/services/healing/resilioPath.js +274 -0
  162. package/lib/src/services/identity/identityChanges.js +41 -0
  163. package/lib/src/services/logcat/SessionDeviceLogs.js +358 -0
  164. package/lib/src/services/logcat/deviceLogBook.js +164 -0
  165. package/lib/src/services/logcat/iosDriverLog.js +178 -0
  166. package/lib/src/services/ocr/ocrData.js +118 -0
  167. package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
  168. package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
  169. package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
  170. package/lib/src/services/passwordResetLink.js +50 -9
  171. package/lib/src/services/passwordResetMode.js +3 -1
  172. package/lib/src/services/publicUrl.js +45 -0
  173. package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
  174. package/lib/src/services/session/sessionIdentity.js +5 -3
  175. package/lib/src/services/session/shutdownReason.js +26 -0
  176. package/lib/src/services/settings/aiEngineSettings.js +219 -0
  177. package/lib/src/services/token/JwtKeyService.js +8 -2
  178. package/lib/src/services/token/mcpTokenTtl.js +27 -0
  179. package/lib/src/services/token/mintedLifetime.js +51 -0
  180. package/lib/src/sessions/LocalSession.js +29 -1
  181. package/lib/src/sessions/RemoteSession.js +12 -2
  182. package/lib/src/sessions/SessionManager.js +4 -1
  183. package/lib/src/sessions/sessionMemory.js +25 -0
  184. package/lib/test/helpers/driver-log.js +38 -0
  185. package/lib/test/helpers/fake-ai-provider.js +188 -0
  186. package/lib/test/helpers/fake-http-proxy.js +106 -0
  187. package/lib/test/helpers/loki-stores.js +33 -0
  188. package/lib/test/helpers/otlp-probe.js +107 -0
  189. package/lib/test/helpers/page-sources.js +164 -0
  190. package/lib/test/helpers/scratch-database.js +57 -13
  191. package/lib/test/integration/androidDevices.spec.js +1 -1
  192. package/lib/test/integration/audit-ingest.spec.js +10 -10
  193. package/lib/test/integration/auth-flow.spec.js +9 -14
  194. package/lib/test/integration/auth-rate-limit.spec.js +6 -3
  195. package/lib/test/integration/bug-report-route.spec.js +22 -2
  196. package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
  197. package/lib/test/integration/forgot-password.spec.js +17 -18
  198. package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
  199. package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
  200. package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
  201. package/lib/test/integration/migration.spec.js +6 -1
  202. package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
  203. package/lib/test/integration/ports-router.spec.js +4 -4
  204. package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
  205. package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
  206. package/lib/test/integration/profile-tokens.spec.js +10 -16
  207. package/lib/test/integration/recordings-router.spec.js +16 -16
  208. package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
  209. package/lib/test/integration/role-matrix.spec.js +4 -9
  210. package/lib/test/integration/sdk-leases-router.spec.js +9 -9
  211. package/lib/test/integration/sdk-version.spec.js +2 -2
  212. package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
  213. package/lib/test/integration/selector-state-flow.spec.js +5 -5
  214. package/lib/test/integration/team-membership-migration.spec.js +73 -12
  215. package/lib/test/integration/team-visibility-apps.spec.js +15 -22
  216. package/lib/test/integration/team-visibility-control.spec.js +96 -69
  217. package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
  218. package/lib/test/integration/team-visibility-grid.spec.js +16 -15
  219. package/lib/test/integration/team-visibility-leases.spec.js +8 -11
  220. package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
  221. package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
  222. package/lib/test/integration/users-flow.spec.js +7 -12
  223. package/lib/test/integration/users-lockout.spec.js +5 -3
  224. package/lib/test/unit/EmailService.test.js +66 -0
  225. package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
  226. package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
  227. package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
  228. package/lib/test/unit/ai-engine-settings.spec.js +338 -0
  229. package/lib/test/unit/ai-provider-switch.spec.js +130 -0
  230. package/lib/test/unit/ai-rate-limit.spec.js +126 -0
  231. package/lib/test/unit/android-adb-retry.spec.js +112 -0
  232. package/lib/test/unit/android-h264-viewers.spec.js +272 -0
  233. package/lib/test/unit/android-preview-hold.spec.js +3 -2
  234. package/lib/test/unit/apikeys-router.spec.js +84 -0
  235. package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
  236. package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
  237. package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
  238. package/lib/test/unit/command-log-network-capture.spec.js +191 -0
  239. package/lib/test/unit/dashboard-events-device-logs.spec.js +255 -0
  240. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  241. package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
  242. package/lib/test/unit/database-check.spec.js +1 -0
  243. package/lib/test/unit/device-log-book.spec.js +180 -0
  244. package/lib/test/unit/device-utils.spec.js +13 -4
  245. package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
  246. package/lib/test/unit/event-log-session-data.spec.js +251 -0
  247. package/lib/test/unit/failure-analysis.spec.js +196 -0
  248. package/lib/test/unit/failure-categories.spec.js +130 -0
  249. package/lib/test/unit/failure-categorization.spec.js +393 -0
  250. package/lib/test/unit/fingerprint-identity.spec.js +433 -0
  251. package/lib/test/unit/h264-multiplexer.spec.js +46 -0
  252. package/lib/test/unit/heal-report.spec.js +71 -0
  253. package/lib/test/unit/healing-instrumentation.spec.js +6 -1
  254. package/lib/test/unit/healing-tier-gate.spec.js +74 -29
  255. package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
  256. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +243 -1
  257. package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
  258. package/lib/test/unit/ios-driver-log.spec.js +168 -0
  259. package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
  260. package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
  261. package/lib/test/unit/lease/lease-override.spec.js +23 -6
  262. package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
  263. package/lib/test/unit/logcat-ws.spec.js +78 -37
  264. package/lib/test/unit/logger-json-levels.spec.js +44 -0
  265. package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
  266. package/lib/test/unit/observability-example-loki.spec.js +25 -0
  267. package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
  268. package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
  269. package/lib/test/unit/ocr-offline.spec.js +213 -0
  270. package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
  271. package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
  272. package/lib/test/unit/omni-vision.spec.js +5 -7
  273. package/lib/test/unit/openapi-for-site.spec.js +80 -0
  274. package/lib/test/unit/outbound-proxy.spec.js +320 -0
  275. package/lib/test/unit/package-engines.spec.js +27 -0
  276. package/lib/test/unit/passwordResetMode.test.js +10 -2
  277. package/lib/test/unit/port-exhaustion.spec.js +2 -1
  278. package/lib/test/unit/profile-router.test.js +122 -1
  279. package/lib/test/unit/proxy-option.spec.js +358 -0
  280. package/lib/test/unit/queue-requester.spec.js +1 -1
  281. package/lib/test/unit/reset-link-address.spec.js +266 -0
  282. package/lib/test/unit/reset-link.test.js +10 -4
  283. package/lib/test/unit/resilio-path-healing.spec.js +270 -0
  284. package/lib/test/unit/run-migrations-database.spec.js +807 -0
  285. package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
  286. package/lib/test/unit/run-migrations.test.js +864 -0
  287. package/lib/test/unit/selector-health-actions.spec.js +3 -1
  288. package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
  289. package/lib/test/unit/selector-state-service.spec.js +72 -23
  290. package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
  291. package/lib/test/unit/selector-verification-job.spec.js +28 -19
  292. package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
  293. package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
  294. package/lib/test/unit/self-healing-boot.spec.js +10 -5
  295. package/lib/test/unit/self-healing-setting.spec.js +7 -6
  296. package/lib/test/unit/session-app-resolution.spec.js +5 -1
  297. package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
  298. package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
  299. package/lib/test/unit/session-credential-strip.spec.js +1 -1
  300. package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
  301. package/lib/test/unit/session-device-logs.spec.js +356 -0
  302. package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
  303. package/lib/test/unit/session-log-indexes.spec.js +64 -3
  304. package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
  305. package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
  306. package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
  307. package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
  308. package/lib/test/unit/token-mint-limits.spec.js +165 -0
  309. package/lib/test/unit/tracing-export-routing.spec.js +137 -0
  310. package/lib/test/unit/tracing-session-spans.spec.js +337 -0
  311. package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
  312. package/lib/test/unit/verify-credential.spec.js +33 -0
  313. package/lib/test/unit/vision-assertion.spec.js +3 -2
  314. package/lib/test/unit/web-config-service.spec.js +19 -0
  315. package/lib/tsconfig.tsbuildinfo +1 -1
  316. package/package.json +6 -5
  317. package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
  318. package/prisma/schema.prisma +3 -0
  319. package/schema.json +16 -12
  320. package/scripts/dev/readme-screenshots.js +276 -23
  321. package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
  322. package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
  323. package/scripts/dev/screenshot-fixtures/session.js +242 -0
  324. package/scripts/export-openapi.js +32 -0
  325. package/scripts/lib/openapi-for-site.js +79 -0
  326. package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
  327. package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
  328. package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
  329. package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
  330. package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
  331. package/lib/public/assets/index-BbKEXUFH.js +0 -219
  332. package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
  333. package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
  334. package/lib/src/scripts/prepare-prisma.js +0 -89
  335. /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
@@ -38,6 +38,15 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  return result;
39
39
  };
40
40
  })();
41
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
42
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
43
+ return new (P || (P = Promise))(function (resolve, reject) {
44
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
45
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
46
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
47
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
48
+ });
49
+ };
41
50
  Object.defineProperty(exports, "__esModule", { value: true });
42
51
  exports.TracingService = void 0;
43
52
  const api_1 = require("@opentelemetry/api");
@@ -53,15 +62,32 @@ const resources_1 = require("@opentelemetry/resources");
53
62
  const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
54
63
  const typedi_1 = require("typedi");
55
64
  const logger_1 = __importStar(require("../logger"));
65
+ /** Why a session's span ended, on the span. */
66
+ const SESSION_STOP_REASON = 'xenon.session.stop_reason';
67
+ function failSpan(span, error) {
68
+ var _a;
69
+ const exception = error instanceof Error ? error : { message: String((_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error) };
70
+ span.recordException(exception);
71
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: exception.message });
72
+ }
56
73
  let TracingService = class TracingService {
57
74
  constructor() {
58
75
  this.sdk = null;
76
+ // Built here only while tracing is off; with it on, the NodeSDK owns them.
59
77
  this.loggerProvider = null;
60
78
  this.meterProvider = null;
79
+ // Whether Xenon's own log export and metric export are configured.
80
+ this.logsOn = false;
81
+ this.metricsOn = false;
61
82
  this.tracer = api_1.trace.getTracer('xenon-core');
62
83
  this.activeSpans = new Map();
84
+ // Which of activeSpans are sessions' (keyed by session id) rather than
85
+ // commands' (`<sessionId>:<command>`).
86
+ this.sessionSpanIds = new Set();
87
+ this.spanProcessorsInUse = [];
63
88
  }
64
89
  initialize(opts = { isHub: true }) {
90
+ var _a;
65
91
  if (process.env.OTEL_SDK_DISABLED === 'true') {
66
92
  logger_1.default.info('[TracingService] OTEL_SDK_DISABLED=true — OTel disabled.');
67
93
  return;
@@ -71,14 +97,56 @@ let TracingService = class TracingService {
71
97
  [semantic_conventions_1.ATTR_SERVICE_NAME]: 'xenon',
72
98
  service: role,
73
99
  });
74
- this.initTraces(resource);
75
- this.initLogs(resource);
76
- this.initMetrics(resource);
100
+ const spanProcessors = this.spanProcessors();
101
+ const logRecordProcessors = this.logRecordProcessors();
102
+ const metricReaders = this.metricReaders();
103
+ if (spanProcessors.length > 0) {
104
+ // A NodeSDK that isn't handed log processors and metric readers builds
105
+ // its own from the standard OTEL_* variables (OTLP exporters at
106
+ // OTEL_EXPORTER_OTLP_ENDPOINT + /v1/logs and /v1/metrics, which is
107
+ // Xenon's trace URL) and registers them globally before Xenon could, so
108
+ // Xenon's settings for logs and metrics did nothing while tracing was
109
+ // on. It gets Xenon's, or none. With none for logs it still registers
110
+ // a logger provider, one that exports nothing.
111
+ this.spanProcessorsInUse = spanProcessors;
112
+ this.sdk = new sdk_node_1.NodeSDK({
113
+ resource,
114
+ spanProcessors,
115
+ logRecordProcessors,
116
+ metricReaders,
117
+ });
118
+ try {
119
+ this.sdk.start();
120
+ logger_1.default.info('[TracingService] Trace SDK started.');
121
+ }
122
+ catch (err) {
123
+ logger_1.default.error(`[TracingService] Failed to start trace SDK: ${err.message}`);
124
+ }
125
+ }
126
+ else {
127
+ if (logRecordProcessors.length > 0) {
128
+ this.loggerProvider = new sdk_logs_1.LoggerProvider({ resource, processors: logRecordProcessors });
129
+ api_logs_1.logs.setGlobalLoggerProvider(this.loggerProvider);
130
+ }
131
+ if (metricReaders.length > 0) {
132
+ this.meterProvider = new sdk_metrics_1.MeterProvider({ resource, readers: metricReaders });
133
+ api_1.metrics.setGlobalMeterProvider(this.meterProvider);
134
+ }
135
+ }
136
+ if (logRecordProcessors.length > 0) {
137
+ this.logsOn = true;
138
+ logger_1.XenonLogger.setOtlpReady(true);
139
+ logger_1.default.info(`[TracingService] Log OTLP endpoint: ${process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT}. Log SDK started.`);
140
+ }
141
+ if (metricReaders.length > 0) {
142
+ this.metricsOn = true;
143
+ logger_1.default.info(`[TracingService] Metric OTLP endpoint: ${(_a = process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) !== null && _a !== void 0 ? _a : '(console-only)'}. Metrics SDK started.`);
144
+ }
77
145
  }
78
- initTraces(resource) {
146
+ spanProcessors() {
79
147
  if (process.env.OTEL_TRACES_ENABLED === 'false') {
80
148
  logger_1.default.info('[TracingService] OTEL_TRACES_ENABLED=false — trace export disabled.');
81
- return;
149
+ return [];
82
150
  }
83
151
  const exporters = [];
84
152
  if (process.env.XENON_OTEL_DEBUG === 'true') {
@@ -93,52 +161,37 @@ let TracingService = class TracingService {
93
161
  }
94
162
  if (exporters.length === 0) {
95
163
  logger_1.default.info('[TracingService] No trace exporters configured. Spans recorded in-memory only.');
96
- return;
164
+ return [];
97
165
  }
98
166
  // Pre-existing pre-PR-86 bug: only exporters[0] was wired, so combining
99
167
  // XENON_OTEL_DEBUG=true with an OTLP endpoint silently dropped one of the
100
168
  // two destinations. Each exporter now gets its own SimpleSpanProcessor.
101
- const spanProcessors = exporters.map((e) => new sdk_trace_base_1.SimpleSpanProcessor(e));
102
- this.sdk = new sdk_node_1.NodeSDK({
103
- resource,
104
- spanProcessors,
105
- });
106
- try {
107
- this.sdk.start();
108
- logger_1.default.info('[TracingService] Trace SDK started.');
109
- }
110
- catch (err) {
111
- logger_1.default.error(`[TracingService] Failed to start trace SDK: ${err.message}`);
112
- }
169
+ return exporters.map((e) => new sdk_trace_base_1.SimpleSpanProcessor(e));
113
170
  }
114
- initLogs(resource) {
171
+ logRecordProcessors() {
115
172
  if (process.env.OTEL_LOGS_ENABLED === 'false') {
116
173
  logger_1.default.info('[TracingService] OTEL_LOGS_ENABLED=false — log export disabled.');
117
- return;
174
+ return [];
118
175
  }
119
176
  const endpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
120
177
  if (!endpoint)
121
- return;
178
+ return [];
122
179
  const processors = [];
123
180
  if (process.env.XENON_OTEL_DEBUG === 'true') {
124
181
  processors.push(new sdk_logs_1.SimpleLogRecordProcessor(new sdk_logs_1.ConsoleLogRecordExporter()));
125
182
  }
126
183
  processors.push(new sdk_logs_1.BatchLogRecordProcessor(new exporter_logs_otlp_http_1.OTLPLogExporter({ url: endpoint })));
127
- const provider = new sdk_logs_1.LoggerProvider({ resource, processors });
128
- api_logs_1.logs.setGlobalLoggerProvider(provider);
129
- this.loggerProvider = provider;
130
- logger_1.XenonLogger.setOtlpReady(true);
131
- logger_1.default.info(`[TracingService] Log OTLP endpoint: ${endpoint}. Log SDK started.`);
184
+ return processors;
132
185
  }
133
- initMetrics(resource) {
186
+ metricReaders() {
134
187
  if (process.env.OTEL_METRICS_ENABLED === 'false') {
135
188
  logger_1.default.info('[TracingService] OTEL_METRICS_ENABLED=false — metrics export disabled.');
136
- return;
189
+ return [];
137
190
  }
138
191
  const endpoint = process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT;
139
192
  const debug = process.env.XENON_OTEL_DEBUG === 'true';
140
193
  if (!endpoint && !debug)
141
- return;
194
+ return [];
142
195
  const readers = [];
143
196
  if (debug) {
144
197
  // 10s interval keeps console output readable during dev sessions.
@@ -154,18 +207,15 @@ let TracingService = class TracingService {
154
207
  exportIntervalMillis: 60000,
155
208
  }));
156
209
  }
157
- const provider = new sdk_metrics_1.MeterProvider({ resource, readers });
158
- api_1.metrics.setGlobalMeterProvider(provider);
159
- this.meterProvider = provider;
160
- logger_1.default.info(`[TracingService] Metric OTLP endpoint: ${endpoint !== null && endpoint !== void 0 ? endpoint : '(console-only)'}. Metrics SDK started.`);
210
+ return readers;
161
211
  }
162
212
  getLogger(name = 'xenon-core') {
163
- if (!this.loggerProvider)
213
+ if (!this.logsOn)
164
214
  return undefined;
165
215
  return api_logs_1.logs.getLogger(name);
166
216
  }
167
217
  getMeter(name = 'xenon-core') {
168
- if (!this.meterProvider)
218
+ if (!this.metricsOn)
169
219
  return undefined;
170
220
  return api_1.metrics.getMeter(name);
171
221
  }
@@ -177,8 +227,18 @@ let TracingService = class TracingService {
177
227
  attributes: Object.assign({ 'xenon.session_id': sessionId }, attributes),
178
228
  });
179
229
  this.activeSpans.set(sessionId, span);
230
+ this.sessionSpanIds.add(sessionId);
180
231
  return span;
181
232
  }
233
+ /**
234
+ * Ends a session's span. Every way a session ends calls this (the client's
235
+ * delete, Appium's new-command timeout, the idle release, the heartbeat
236
+ * sweeps, a shutdown's drain), so whichever comes second finds nothing.
237
+ * `failed` for a session that ended failed; `reason` says why it ended.
238
+ */
239
+ endSessionSpan(sessionId, ending = {}) {
240
+ this.endSpan(sessionId, ending.failed ? 'ERROR' : 'OK', ending.reason ? { [SESSION_STOP_REASON]: ending.reason } : {});
241
+ }
182
242
  startCommandSpan(sessionId, commandName, attributes = {}) {
183
243
  if (!this.sdk)
184
244
  return undefined;
@@ -202,28 +262,34 @@ let TracingService = class TracingService {
202
262
  if (!this.sdk)
203
263
  return;
204
264
  const span = this.activeSpans.get(id);
205
- if (span) {
206
- if (Object.keys(attributes).length > 0) {
207
- span.setAttributes(attributes);
208
- }
209
- span.setStatus({
210
- code: status === 'OK' ? api_1.SpanStatusCode.OK : api_1.SpanStatusCode.ERROR,
211
- });
212
- span.end();
213
- this.activeSpans.delete(id);
214
- }
215
- }
216
- recordError(id, error) {
217
- if (!this.sdk)
265
+ if (!span)
218
266
  return;
219
- const span = this.activeSpans.get(id);
220
- if (span) {
221
- span.recordException(error);
222
- span.setStatus({
223
- code: api_1.SpanStatusCode.ERROR,
224
- message: typeof error === 'string' ? error : error.message,
225
- });
267
+ this.activeSpans.delete(id);
268
+ this.sessionSpanIds.delete(id);
269
+ if (Object.keys(attributes).length > 0) {
270
+ span.setAttributes(attributes);
226
271
  }
272
+ span.setStatus({
273
+ code: status === 'OK' ? api_1.SpanStatusCode.OK : api_1.SpanStatusCode.ERROR,
274
+ });
275
+ span.end();
276
+ }
277
+ /**
278
+ * Ends the span a command started: ERROR with the exception when it
279
+ * `failed`, else OK. By the span itself, not by its id: two commands of the
280
+ * same name on one session at once (the Inspector reading the page source
281
+ * while a test does) share `<sessionId>:<command>`, and the second's span
282
+ * replaced the first's under it, so one command's error went on the other's
283
+ * span and its own never ended.
284
+ */
285
+ endCommandSpan(id, span, failed) {
286
+ if (failed)
287
+ failSpan(span, failed.error);
288
+ else
289
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
290
+ span.end();
291
+ if (this.activeSpans.get(id) === span)
292
+ this.activeSpans.delete(id);
227
293
  }
228
294
  getTraceId(sessionId) {
229
295
  var _a;
@@ -233,12 +299,57 @@ let TracingService = class TracingService {
233
299
  var _a;
234
300
  return (_a = this.activeSpans.get(id)) === null || _a === void 0 ? void 0 : _a.spanContext().spanId;
235
301
  }
302
+ /**
303
+ * shutdown(), waiting no longer than `budgetMs`: an export to a collector
304
+ * that doesn't answer is held for the exporter's own timeout (10 s), and a
305
+ * process that is exiting can't wait that long.
306
+ */
307
+ shutdownWithin(budgetMs) {
308
+ return __awaiter(this, void 0, void 0, function* () {
309
+ let timer;
310
+ try {
311
+ yield Promise.race([
312
+ this.shutdown(),
313
+ new Promise((resolve) => {
314
+ timer = setTimeout(resolve, budgetMs);
315
+ }),
316
+ ]);
317
+ }
318
+ finally {
319
+ if (timer)
320
+ clearTimeout(timer);
321
+ }
322
+ });
323
+ }
324
+ /**
325
+ * Stops every exporter, once what each still holds is sent. A session span
326
+ * still open belongs to a session this process didn't end: one running on a
327
+ * node or at a cloud provider, which a hub's shutdown leaves running, or one
328
+ * whose ending was missed. It ends here, saying so, or it is never exported.
329
+ */
236
330
  shutdown() {
237
- var _a, _b, _c;
238
- (_a = this.sdk) === null || _a === void 0 ? void 0 : _a.shutdown();
239
- logger_1.XenonLogger.setOtlpReady(false);
240
- (_b = this.loggerProvider) === null || _b === void 0 ? void 0 : _b.shutdown();
241
- (_c = this.meterProvider) === null || _c === void 0 ? void 0 : _c.shutdown();
331
+ return __awaiter(this, void 0, void 0, function* () {
332
+ for (const sessionId of [...this.sessionSpanIds]) {
333
+ const span = this.activeSpans.get(sessionId);
334
+ this.activeSpans.delete(sessionId);
335
+ span === null || span === void 0 ? void 0 : span.setAttribute(SESSION_STOP_REASON, 'Xenon shut down while the session was running');
336
+ span === null || span === void 0 ? void 0 : span.end();
337
+ }
338
+ this.sessionSpanIds.clear();
339
+ // A span processor's shutdown doesn't wait for the exports it started,
340
+ // and with the NodeSDK's resource detection every export waits on it.
341
+ yield Promise.allSettled(this.spanProcessorsInUse.map((p) => p.forceFlush()));
342
+ this.spanProcessorsInUse = [];
343
+ logger_1.XenonLogger.setOtlpReady(false);
344
+ const stopping = [this.sdk, this.loggerProvider, this.meterProvider].map((owner) => owner === null || owner === void 0 ? void 0 : owner.shutdown());
345
+ this.sdk = null;
346
+ this.loggerProvider = null;
347
+ this.meterProvider = null;
348
+ this.logsOn = false;
349
+ this.metricsOn = false;
350
+ this.activeSpans.clear();
351
+ yield Promise.allSettled(stopping);
352
+ });
242
353
  }
243
354
  };
244
355
  exports.TracingService = TracingService;
@@ -24,6 +24,7 @@ const bcrypt_1 = __importDefault(require("bcrypt"));
24
24
  const crypto_1 = __importDefault(require("crypto"));
25
25
  const logger_1 = __importDefault(require("../logger"));
26
26
  const prisma_1 = require("../prisma");
27
+ const identityChanges_1 = require("./identity/identityChanges");
27
28
  const ACCESS_KEY_PREFIX = 'xen_';
28
29
  const ACCESS_KEY_LEN = 12;
29
30
  const PASSWORD_MIN = 8;
@@ -92,14 +93,18 @@ let UserService = class UserService {
92
93
  return prisma_1.prisma.user.findUnique({ where: { id } });
93
94
  });
94
95
  }
96
+ // The old access key's (accessKey, token) pairs stop working, a node's
97
+ // socket's included.
95
98
  rotateAccessKey(userId) {
96
99
  return __awaiter(this, void 0, void 0, function* () {
97
100
  const accessKey = this.generateAccessKey();
98
- return prisma_1.prisma.user.update({
101
+ const updated = yield prisma_1.prisma.user.update({
99
102
  where: { id: userId },
100
103
  data: { accessKey },
101
104
  select: { id: true, accessKey: true },
102
105
  });
106
+ yield (0, identityChanges_1.identityChanged)(userId);
107
+ return updated;
103
108
  });
104
109
  }
105
110
  changePassword(userId, oldPassword, newPassword) {
@@ -163,18 +168,23 @@ let UserService = class UserService {
163
168
  data.role = patch.role;
164
169
  if (patch.status !== undefined)
165
170
  data.status = patch.status;
166
- return prisma_1.prisma.user.update({ where: { id: userId }, data });
171
+ const updated = yield prisma_1.prisma.user.update({ where: { id: userId }, data });
172
+ yield (0, identityChanges_1.identityChanged)(userId);
173
+ return updated;
167
174
  });
168
175
  }
169
176
  setRole(userId, role) {
170
177
  return __awaiter(this, void 0, void 0, function* () {
171
- return prisma_1.prisma.user.update({ where: { id: userId }, data: { role } });
178
+ const updated = yield prisma_1.prisma.user.update({ where: { id: userId }, data: { role } });
179
+ yield (0, identityChanges_1.identityChanged)(userId);
180
+ return updated;
172
181
  });
173
182
  }
174
183
  deactivateUser(userId) {
175
184
  return __awaiter(this, void 0, void 0, function* () {
176
185
  yield prisma_1.prisma.user.update({ where: { id: userId }, data: { status: 'INACTIVE' } });
177
186
  yield prisma_1.prisma.userSession.deleteMany({ where: { userId } });
187
+ yield (0, identityChanges_1.identityChanged)(userId);
178
188
  });
179
189
  }
180
190
  deleteUser(userId) {
@@ -184,6 +194,7 @@ let UserService = class UserService {
184
194
  // relying on cascade semantics for security-critical cleanup.
185
195
  yield prisma_1.prisma.userSession.deleteMany({ where: { userId } });
186
196
  yield prisma_1.prisma.user.delete({ where: { id: userId } });
197
+ yield (0, identityChanges_1.identityChanged)(userId);
187
198
  });
188
199
  }
189
200
  };
@@ -22,6 +22,7 @@ exports.UserSessionService = void 0;
22
22
  const typedi_1 = require("typedi");
23
23
  const prisma_1 = require("../prisma");
24
24
  const logger_1 = __importDefault(require("../logger"));
25
+ const identityChanges_1 = require("./identity/identityChanges");
25
26
  const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
26
27
  let UserSessionService = class UserSessionService {
27
28
  constructor() {
@@ -38,13 +39,20 @@ let UserSessionService = class UserSessionService {
38
39
  });
39
40
  });
40
41
  }
41
- resolve(sessionId) {
42
- return __awaiter(this, void 0, void 0, function* () {
42
+ /**
43
+ * The sign-in, if it exists and hasn't expired. A use of it renews it
44
+ * (sliding TTL); `renew: false` only looks, for a check that isn't a use: a
45
+ * socket's re-check must not keep a sign-in alive for as long as a tab is open.
46
+ */
47
+ resolve(sessionId_1) {
48
+ return __awaiter(this, arguments, void 0, function* (sessionId, { renew = true } = {}) {
43
49
  const row = yield prisma_1.prisma.userSession.findUnique({ where: { id: sessionId } });
44
50
  if (!row)
45
51
  return null;
46
52
  if (row.expiresAt.getTime() < Date.now())
47
53
  return null;
54
+ if (!renew)
55
+ return row;
48
56
  const newExpiresAt = new Date(Date.now() + this.ttlMs());
49
57
  // Fire-and-forget sliding-TTL update; failures must not block auth, but
50
58
  // log them at debug level so chronic write issues are diagnosable.
@@ -62,13 +70,15 @@ let UserSessionService = class UserSessionService {
62
70
  // which is fine. Other errors get logged for diagnosis but don't bubble.
63
71
  revoke(sessionId) {
64
72
  return __awaiter(this, void 0, void 0, function* () {
65
- yield prisma_1.prisma.userSession.delete({ where: { id: sessionId } }).catch((e) => {
73
+ const row = yield prisma_1.prisma.userSession.delete({ where: { id: sessionId } }).catch((e) => {
66
74
  var _a;
67
75
  const code = e === null || e === void 0 ? void 0 : e.code;
68
76
  if (code === 'P2025')
69
- return;
77
+ return null;
70
78
  this.log.warn(`revoke: unexpected delete error for session ${sessionId}: ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}`);
79
+ return null;
71
80
  });
81
+ yield (0, identityChanges_1.identityChanged)(row === null || row === void 0 ? void 0 : row.userId);
72
82
  });
73
83
  }
74
84
  revokeAllForUserExcept(userId, keepSessionId) {
@@ -78,6 +88,7 @@ let UserSessionService = class UserSessionService {
78
88
  });
79
89
  if (r.count > 0) {
80
90
  this.log.info(`revoked ${r.count} sessions for user ${userId} (kept ${keepSessionId})`);
91
+ yield (0, identityChanges_1.identityChanged)(userId);
81
92
  }
82
93
  return r.count;
83
94
  });
@@ -87,6 +98,7 @@ let UserSessionService = class UserSessionService {
87
98
  const r = yield prisma_1.prisma.userSession.deleteMany({ where: { userId } });
88
99
  if (r.count > 0) {
89
100
  this.log.info(`revoked all ${r.count} sessions for user ${userId}`);
101
+ yield (0, identityChanges_1.identityChanged)(userId);
90
102
  }
91
103
  return r.count;
92
104
  });
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.sliceVideo = sliceVideo;
7
7
  const child_process_1 = require("child_process");
8
+ const ffmpegPath_1 = require("../../helpers/ffmpegPath");
8
9
  const logger_1 = __importDefault(require("../../logger"));
9
10
  function sliceVideo(inputPath, startSec, endSec, outPath, spawnFn = child_process_1.spawn) {
10
11
  return new Promise((resolve) => {
@@ -16,7 +17,9 @@ function sliceVideo(inputPath, startSec, endSec, outPath, spawnFn = child_proces
16
17
  '-c', 'copy',
17
18
  outPath,
18
19
  ];
19
- const proc = spawnFn('ffmpeg', args);
20
+ // Xenon's own ffmpeg, as everywhere else: a bare name isn't found by a
21
+ // server the Mac app started, and finds a system ffmpeg first otherwise.
22
+ const proc = spawnFn((0, ffmpegPath_1.resolveFfmpegPath)(), args);
20
23
  let stderr = '';
21
24
  if (proc.stderr) {
22
25
  proc.stderr.on('data', (chunk) => {
@@ -2,18 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.canOverrideLease = canOverrideLease;
4
4
  function canOverrideLease(credential) {
5
- var _a, _b;
6
5
  switch (credential.kind) {
7
6
  case 'auth-disabled':
8
7
  return true;
9
- case 'api-key': {
8
+ case 'api-key':
9
+ case 'session-token': {
10
10
  if (!credential.user)
11
11
  return false;
12
12
  const scopes = new Set(credential.scopes.split(',').map((s) => s.trim()));
13
- return scopes.has('admin') || credential.user.role === 'SUPER_ADMIN';
13
+ const { role } = credential.user;
14
+ if (credential.kind === 'session-token') {
15
+ return role === 'SUPER_ADMIN' || (role === 'ADMIN' && scopes.has('admin'));
16
+ }
17
+ return scopes.has('admin') || role === 'SUPER_ADMIN';
14
18
  }
15
- case 'session-token':
16
- return ((_a = credential.user) === null || _a === void 0 ? void 0 : _a.role) === 'SUPER_ADMIN' || ((_b = credential.user) === null || _b === void 0 ? void 0 : _b.role) === 'ADMIN';
17
19
  default:
18
20
  return false;
19
21
  }
@@ -20,6 +20,9 @@ const xpath_1 = require("xpath");
20
20
  const typedi_1 = require("typedi");
21
21
  const logger_1 = __importDefault(require("../../logger"));
22
22
  const HealedLocatorGenerator_1 = require("./HealedLocatorGenerator");
23
+ const fingerprintIdentity_1 = require("./fingerprintIdentity");
24
+ /** What an element says in words, for telling it apart once its identifier differs. */
25
+ const TEXTS = ['text', 'label', 'content-desc'];
23
26
  class FuzzyXmlHealingProvider {
24
27
  constructor(etalonService) {
25
28
  this.etalonService = etalonService;
@@ -41,6 +44,12 @@ class FuzzyXmlHealingProvider {
41
44
  if (this.etalonService) {
42
45
  etalon = yield this.etalonService.getSignature(context.selector);
43
46
  }
47
+ // A fingerprint with only a rect and a type (every learnt one through
48
+ // 2.14) would heal to whatever element of that type is at that spot.
49
+ if (etalon && !(0, fingerprintIdentity_1.hasIdentity)(etalon.attributes)) {
50
+ this.logger.info('The baseline signature says nothing about which element it is; matching without it.');
51
+ etalon = null;
52
+ }
44
53
  const keywords = this.extractKeywords(context.selector);
45
54
  if (keywords.length === 0 && !etalon)
46
55
  return null;
@@ -135,6 +144,9 @@ class FuzzyXmlHealingProvider {
135
144
  if (etalonTag !== nodeTag && etalonTag !== 'xcuielementtypeany' && etalonTag !== 'unknown') {
136
145
  return 0; // Immediate rejection — wrong element type
137
146
  }
147
+ // Position can't heal to an element whose identifier says it's another one.
148
+ if (this.contradictsIdentifier(node, etalon))
149
+ return 0;
138
150
  // Tag matches perfectly
139
151
  totalWeight += 1.0;
140
152
  totalScore += 1.0;
@@ -260,6 +272,45 @@ class FuzzyXmlHealingProvider {
260
272
  }
261
273
  return finalScore;
262
274
  }
275
+ /**
276
+ * Whether `node` is, by its identifier, another element than the
277
+ * fingerprint's: the fingerprint has an identifier (an Android resource-id,
278
+ * compared without its package, or an iOS accessibility identifier), `node`
279
+ * has another one (sameIdentifier), and no text of it (text, label,
280
+ * content-desc) reads the same as the fingerprint's (sameText). Its
281
+ * position then counts for nothing.
282
+ *
283
+ * A renamed id that kept its text or description still heals, and so does
284
+ * a renamed text that kept its id. An element without an identifier, where
285
+ * the fingerprint has one, is judged by its text alone: an Android element
286
+ * with no resource-id, or an iOS one, whose name is then its label. Only
287
+ * where neither has one does position decide, as through 2.14.
288
+ * What this stops is the button that took the old one's place: "Cancel
289
+ * order" (cancel) where "Pay now" (pay) was, "Log out" where "Log in" was.
290
+ */
291
+ contradictsIdentifier(node, etalon) {
292
+ const identifier = (0, fingerprintIdentity_1.stableIdentifierOf)(etalon.attributes);
293
+ if (!identifier)
294
+ return false;
295
+ // Another identifier, or none where the fingerprint has one, and the
296
+ // text decides. iOS gives an element without one its label as name.
297
+ const theirs = this.getAttrValue(node, identifier.attribute);
298
+ if ((0, fingerprintIdentity_1.meaningful)(theirs)) {
299
+ const [mine, other] = identifier.attribute === 'resource-id'
300
+ ? [(0, fingerprintIdentity_1.resourceIdName)(identifier.value), (0, fingerprintIdentity_1.resourceIdName)(theirs)]
301
+ : [identifier.value, theirs];
302
+ if ((0, fingerprintIdentity_1.sameIdentifier)(mine, other))
303
+ return false;
304
+ }
305
+ const texts = TEXTS.map((name) => etalon.attributes[name]).filter(fingerprintIdentity_1.meaningful);
306
+ if (texts.length === 0)
307
+ return true;
308
+ const nodeTexts = [
309
+ node.textContent,
310
+ ...TEXTS.map((name) => this.getAttrValue(node, name)),
311
+ ].filter(fingerprintIdentity_1.meaningful);
312
+ return !nodeTexts.some((t) => texts.some((w) => (0, fingerprintIdentity_1.sameText)(t, w)));
313
+ }
263
314
  getAttrValue(node, name) {
264
315
  if (typeof node.getAttribute === 'function') {
265
316
  const val = node.getAttribute(name);
@@ -37,7 +37,9 @@ let HealEtalonService = class HealEtalonService {
37
37
  return __awaiter(this, void 0, void 0, function* () {
38
38
  try {
39
39
  const attributes = {};
40
- // Extract identifying attributes
40
+ // Extract identifying attributes. Not `value`: on a text field it is
41
+ // what the test typed, an email or a name, which says nothing about
42
+ // which field it is.
41
43
  const nodeAttrs = node.attributes || [];
42
44
  const anchorNames = [
43
45
  'content-desc',
@@ -48,7 +50,6 @@ let HealEtalonService = class HealEtalonService {
48
50
  'hint',
49
51
  'label',
50
52
  'accessibility-id',
51
- 'value',
52
53
  'x',
53
54
  'y',
54
55
  'width',