@vertesia/ui 1.5.0-dev.20260804.124748Z → 1.5.0-dev.20260807.073259Z

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 (299) hide show
  1. package/lib/core/components/ComboBox.d.ts.map +1 -1
  2. package/lib/core/components/ComboBox.js +4 -4
  3. package/lib/core/components/ComboBox.js.map +1 -1
  4. package/lib/core/components/FileUpload.js.map +1 -1
  5. package/lib/core/components/MenuList.d.ts +2 -1
  6. package/lib/core/components/MenuList.d.ts.map +1 -1
  7. package/lib/core/components/TagsInput.js +1 -1
  8. package/lib/core/components/popup/PopupController.d.ts.map +1 -1
  9. package/lib/core/components/popup/PopupController.js.map +1 -1
  10. package/lib/core/components/popup/position.js.map +1 -1
  11. package/lib/core/components/popup/utils.js.map +1 -1
  12. package/lib/core/components/shadcn/LanguageSwitcher.js.map +1 -1
  13. package/lib/core/components/shadcn/MessageBox.js.map +1 -1
  14. package/lib/core/components/shadcn/Panel.js +1 -1
  15. package/lib/core/components/shadcn/badge.d.ts +2 -2
  16. package/lib/core/components/shadcn/button.d.ts +2 -2
  17. package/lib/core/components/shadcn/button.js +1 -1
  18. package/lib/core/components/shadcn/command.d.ts +4 -4
  19. package/lib/core/components/shadcn/command.d.ts.map +1 -1
  20. package/lib/core/components/shadcn/filters/DynamicLabel.js +1 -1
  21. package/lib/core/components/shadcn/filters/comboBox/DateCombobox.d.ts.map +1 -1
  22. package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js +1 -1
  23. package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.d.ts.map +1 -1
  24. package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js +1 -1
  25. package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.d.ts.map +1 -1
  26. package/lib/core/components/shadcn/filters/comboBox/TextCombobox.d.ts.map +1 -1
  27. package/lib/core/components/shadcn/filters/filter/dateFilter.js +1 -1
  28. package/lib/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
  29. package/lib/core/components/shadcn/filters/filters.js.map +1 -1
  30. package/lib/core/components/shadcn/input.d.ts +2 -2
  31. package/lib/core/components/shadcn/modal/dialog.d.ts.map +1 -1
  32. package/lib/core/components/shadcn/popover.d.ts +1 -1
  33. package/lib/core/components/shadcn/popover.d.ts.map +1 -1
  34. package/lib/core/components/shadcn/popover.js.map +1 -1
  35. package/lib/core/components/shadcn/radioGroup.d.ts.map +1 -1
  36. package/lib/core/components/shadcn/selectBox.js +1 -1
  37. package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
  38. package/lib/core/components/shadcn/tabs.js +1 -1
  39. package/lib/core/components/shadcn/text.d.ts +2 -2
  40. package/lib/core/components/shadcn/theme/ThemeSwitcher.js.map +1 -1
  41. package/lib/core/components/toast/ToastProvider.js +1 -1
  42. package/lib/core/hooks/CompositeState.d.ts +1 -1
  43. package/lib/core/hooks/CompositeState.d.ts.map +1 -1
  44. package/lib/core/hooks/CompositeState.js.map +1 -1
  45. package/lib/core/hooks/SharedState.d.ts.map +1 -1
  46. package/lib/core/hooks/useFetch.d.ts.map +1 -1
  47. package/lib/core/hooks/useFetch.js +2 -1
  48. package/lib/core/hooks/useFetch.js.map +1 -1
  49. package/lib/core/hooks/useFlag.d.ts.map +1 -1
  50. package/lib/core/hooks/useIntersectionObserver.js.map +1 -1
  51. package/lib/core/hooks/useSafeLayoutEffect.d.ts +2 -2
  52. package/lib/core/hooks/useSafeLayoutEffect.d.ts.map +1 -1
  53. package/lib/core/hooks/useScrollableSearch.js.map +1 -1
  54. package/lib/core/hooks/useSharedValue.d.ts.map +1 -1
  55. package/lib/core/hooks/useSharedValue.js.map +1 -1
  56. package/lib/core/utils/a11y.d.ts.map +1 -1
  57. package/lib/env/index.d.ts.map +1 -1
  58. package/lib/env/index.js.map +1 -1
  59. package/lib/features/agent/PayloadBuilder.d.ts +1 -1
  60. package/lib/features/agent/PayloadBuilder.d.ts.map +1 -1
  61. package/lib/features/agent/PayloadBuilder.js.map +1 -1
  62. package/lib/features/agent/chat/ArtifactsTab.js +1 -1
  63. package/lib/features/agent/chat/AskUserWidget.js +1 -1
  64. package/lib/features/agent/chat/DocumentEditingActionCard.js +1 -1
  65. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  66. package/lib/features/agent/chat/ModernAgentConversation.js +26 -3
  67. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  68. package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.js +1 -1
  69. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts.map +1 -1
  70. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js.map +1 -1
  71. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  72. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +1 -1
  73. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts +7 -0
  74. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts.map +1 -1
  75. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js +25 -0
  76. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js.map +1 -1
  77. package/lib/features/agent/chat/ModernAgentOutput/BatchProgressPanel.d.ts.map +1 -1
  78. package/lib/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts.map +1 -1
  79. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  80. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js +1 -1
  81. package/lib/features/agent/chat/ModernAgentOutput/SlideInPanel.js +1 -1
  82. package/lib/features/agent/chat/ModernAgentOutput/StreamingMessage.d.ts.map +1 -1
  83. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.d.ts.map +1 -1
  84. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js +8 -0
  85. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js.map +1 -1
  86. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.d.ts.map +1 -1
  87. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js +1 -1
  88. package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js +1 -1
  89. package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts.map +1 -1
  90. package/lib/features/agent/chat/SlidingThinkingIndicator.js +1 -1
  91. package/lib/features/agent/chat/VegaLiteChart.js +1 -1
  92. package/lib/features/agent/chat/VegaLiteChart.js.map +1 -1
  93. package/lib/features/agent/chat/hooks/useAgentStream.d.ts.map +1 -1
  94. package/lib/features/agent/chat/hooks/useAgentStream.js +26 -0
  95. package/lib/features/agent/chat/hooks/useAgentStream.js.map +1 -1
  96. package/lib/features/agent/chat/hooks/useFileProcessing.d.ts +8 -1
  97. package/lib/features/agent/chat/hooks/useFileProcessing.d.ts.map +1 -1
  98. package/lib/features/agent/chat/hooks/useFileProcessing.js +31 -2
  99. package/lib/features/agent/chat/hooks/useFileProcessing.js.map +1 -1
  100. package/lib/features/agent/chat/useArtifactUrlCache.js.map +1 -1
  101. package/lib/features/agent/chat/workstreams.d.ts +1 -1
  102. package/lib/features/facets/AgentRunnerFacetsNav.d.ts.map +1 -1
  103. package/lib/features/facets/CollectionsFacetsNav.d.ts.map +1 -1
  104. package/lib/features/facets/DocumentsFacetsNav.d.ts.map +1 -1
  105. package/lib/features/facets/InteractionsFacetsNav.d.ts.map +1 -1
  106. package/lib/features/facets/PromptsFacetsNav.d.ts.map +1 -1
  107. package/lib/features/facets/RunsFacetsNav.d.ts.map +1 -1
  108. package/lib/features/facets/WorkflowExecutionsFacetsNav.d.ts.map +1 -1
  109. package/lib/features/magic-pdf/GroundedExtractionView.js +1 -1
  110. package/lib/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -1
  111. package/lib/features/magic-pdf/MagicPdfProvider.js.map +1 -1
  112. package/lib/features/oauth/McpConnectionsButton.js +1 -1
  113. package/lib/features/oauth/OAuthProviderConnectButton.js +1 -1
  114. package/lib/features/oauth/RemoteMcpConnectionButton.js +1 -1
  115. package/lib/features/pdf-viewer/PdfPageRenderer.js +1 -1
  116. package/lib/features/pdf-viewer/PdfPageSlider.js +1 -1
  117. package/lib/features/permissions/UserPermissionsProvider.d.ts.map +1 -1
  118. package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
  119. package/lib/features/store/collections/CollectionsTable.js +2 -2
  120. package/lib/features/store/collections/CollectionsTable.js.map +1 -1
  121. package/lib/features/store/collections/CreateCollection.js +1 -1
  122. package/lib/features/store/collections/EditCollectionView.js +1 -1
  123. package/lib/features/store/objects/DocumentQuickFilter.js +1 -1
  124. package/lib/features/store/objects/DocumentSelectionProvider.d.ts.map +1 -1
  125. package/lib/features/store/objects/DocumentSelectionProvider.js.map +1 -1
  126. package/lib/features/store/objects/components/ContentDispositionButton.d.ts +2 -1
  127. package/lib/features/store/objects/components/ContentDispositionButton.d.ts.map +1 -1
  128. package/lib/features/store/objects/components/ContentOverview.js +1 -1
  129. package/lib/features/store/objects/components/DocumentEditingPanel.js +1 -1
  130. package/lib/features/store/objects/components/PropertiesEditorModal.js +1 -1
  131. package/lib/features/store/objects/components/TextEditorPanel.js +1 -1
  132. package/lib/features/store/objects/components/documentEditingTarget.d.ts.map +1 -1
  133. package/lib/features/store/objects/components/useContentPanelHooks.d.ts +2 -2
  134. package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
  135. package/lib/features/store/objects/layout/DocumentTableColumn.d.ts +1 -1
  136. package/lib/features/store/objects/layout/DocumentTableColumn.d.ts.map +1 -1
  137. package/lib/features/store/objects/layout/DocumentTableColumn.js.map +1 -1
  138. package/lib/features/store/objects/layout/documentLayout.js +1 -1
  139. package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
  140. package/lib/features/store/objects/search/DocumentSearchContext.d.ts +25 -25
  141. package/lib/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
  142. package/lib/features/store/objects/search/DocumentSearchContext.js.map +1 -1
  143. package/lib/features/store/objects/selection/ObjectsActionContextClass.d.ts.map +1 -1
  144. package/lib/features/store/objects/selection/ObjectsActionContextClass.js.map +1 -1
  145. package/lib/features/store/objects/selection/SelectionActions.js +1 -1
  146. package/lib/features/store/objects/selection/actions/ConfirmAction.js +1 -1
  147. package/lib/features/store/objects/selection/actions/DeleteObjectsAction.js +1 -1
  148. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +1 -1
  149. package/lib/features/store/objects/upload/DocumentUploadModal.js +1 -1
  150. package/lib/features/store/objects/upload/useSmartFileUploadProcessing.d.ts.map +1 -1
  151. package/lib/features/store/objects/upload/useUploadHandler.d.ts.map +1 -1
  152. package/lib/features/store/types/ContentObjectTypesSearch.js +1 -1
  153. package/lib/features/store/types/IntakePolicyEditor.js +1 -1
  154. package/lib/features/store/types/TypeRegistry.d.ts +240 -180
  155. package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
  156. package/lib/features/store/types/TypeRegistry.js.map +1 -1
  157. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +239 -179
  158. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
  159. package/lib/features/store/types/search/ObjectTypeSearchContext.js.map +1 -1
  160. package/lib/features/user/UserInfo.d.ts +14 -14
  161. package/lib/features/user/UserInfo.js +1 -1
  162. package/lib/features/user/UserInfo.js.map +1 -1
  163. package/lib/features/utils/mimeType.d.ts.map +1 -1
  164. package/lib/features/utils/rendition.js.map +1 -1
  165. package/lib/features/utils/text.js.map +1 -1
  166. package/lib/i18n/locales/ar.json +25 -0
  167. package/lib/i18n/locales/de.json +25 -0
  168. package/lib/i18n/locales/en.json +25 -0
  169. package/lib/i18n/locales/es.json +25 -0
  170. package/lib/i18n/locales/fr.json +25 -0
  171. package/lib/i18n/locales/it.json +25 -0
  172. package/lib/i18n/locales/ja.json +25 -0
  173. package/lib/i18n/locales/ko.json +25 -0
  174. package/lib/i18n/locales/pt.json +25 -0
  175. package/lib/i18n/locales/ru.json +25 -0
  176. package/lib/i18n/locales/tr.json +25 -0
  177. package/lib/i18n/locales/zh-TW.json +25 -0
  178. package/lib/i18n/locales/zh.json +25 -0
  179. package/lib/i18n/rtl.d.ts +1 -1
  180. package/lib/i18n/rtl.d.ts.map +1 -1
  181. package/lib/layout/FullHeightLayout.d.ts +7 -6
  182. package/lib/layout/FullHeightLayout.d.ts.map +1 -1
  183. package/lib/layout/Sidebar.d.ts +1 -1
  184. package/lib/layout/Sidebar.js +1 -1
  185. package/lib/rich-text/EditorToolbar.js +1 -1
  186. package/lib/router/HistoryNavigator.d.ts +2 -2
  187. package/lib/router/HistoryNavigator.d.ts.map +1 -1
  188. package/lib/router/HistoryNavigator.js.map +1 -1
  189. package/lib/router/PathMatcher.d.ts.map +1 -1
  190. package/lib/router/PathMatcher.js.map +1 -1
  191. package/lib/router/PathWithParams.d.ts.map +1 -1
  192. package/lib/router/Redirect.js.map +1 -1
  193. package/lib/router/Route404.js +1 -1
  194. package/lib/router/Router.d.ts.map +1 -1
  195. package/lib/router/Router.js.map +1 -1
  196. package/lib/session/UserSession.d.ts +1 -1
  197. package/lib/session/UserSession.d.ts.map +1 -1
  198. package/lib/session/UserSession.js.map +1 -1
  199. package/lib/session/UserSessionProvider.d.ts +2 -0
  200. package/lib/session/UserSessionProvider.d.ts.map +1 -1
  201. package/lib/session/UserSessionProvider.js +81 -53
  202. package/lib/session/UserSessionProvider.js.map +1 -1
  203. package/lib/session/auth/composable.d.ts +49 -1
  204. package/lib/session/auth/composable.d.ts.map +1 -1
  205. package/lib/session/auth/composable.js +179 -53
  206. package/lib/session/auth/composable.js.map +1 -1
  207. package/lib/shell/VertesiaShell.js +1 -1
  208. package/lib/shell/apps/AppInstallationProvider.d.ts +58 -50
  209. package/lib/shell/apps/AppInstallationProvider.d.ts.map +1 -1
  210. package/lib/shell/apps/AppProjectSelector.js +1 -1
  211. package/lib/shell/login/SignInEmailStep.js +1 -1
  212. package/lib/shell/login/SignInPrimitives.d.ts +21 -20
  213. package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
  214. package/lib/shell/login/SignInPrimitives.js.map +1 -1
  215. package/lib/shell/login/SignInRecoveryStep.d.ts +22 -0
  216. package/lib/shell/login/SignInRecoveryStep.d.ts.map +1 -0
  217. package/lib/shell/login/SignInRecoveryStep.js +59 -0
  218. package/lib/shell/login/SignInRecoveryStep.js.map +1 -0
  219. package/lib/shell/login/SigninScreen.d.ts.map +1 -1
  220. package/lib/shell/login/SigninScreen.js +84 -11
  221. package/lib/shell/login/SigninScreen.js.map +1 -1
  222. package/lib/shell/login/SignupForm.js +1 -1
  223. package/lib/shell/login/TerminalLogin.js +1 -1
  224. package/lib/shell/login/UserSessionMenu.js +1 -1
  225. package/lib/vertesia-ui-core.js.map +1 -1
  226. package/lib/vertesia-ui-features.js +26 -26
  227. package/lib/vertesia-ui-features.js.map +1 -1
  228. package/lib/vertesia-ui-i18n.js +13 -13
  229. package/lib/vertesia-ui-layout.js.map +1 -1
  230. package/lib/vertesia-ui-rich-text.js.map +1 -1
  231. package/lib/vertesia-ui-router.js.map +1 -1
  232. package/lib/vertesia-ui-session.js +1 -1
  233. package/lib/vertesia-ui-session.js.map +1 -1
  234. package/lib/vertesia-ui-shell.js +1 -1
  235. package/lib/vertesia-ui-shell.js.map +1 -1
  236. package/lib/vertesia-ui-widgets.js.map +1 -1
  237. package/lib/widgets/form/FormContext.d.ts.map +1 -1
  238. package/lib/widgets/form/FormContext.js.map +1 -1
  239. package/lib/widgets/form/ManagedObject.d.ts +2 -2
  240. package/lib/widgets/form/ManagedObject.d.ts.map +1 -1
  241. package/lib/widgets/form/ManagedObject.js.map +1 -1
  242. package/lib/widgets/form/schema.d.ts.map +1 -1
  243. package/lib/widgets/form/schema.js.map +1 -1
  244. package/lib/widgets/json-view/JSONCode.js +1 -1
  245. package/lib/widgets/json-view/JSONTable.js +1 -1
  246. package/lib/widgets/markdown/ArtifactContentRenderer.js.map +1 -1
  247. package/lib/widgets/markdown/CodeBlockPlaceholder.d.ts +1 -1
  248. package/lib/widgets/markdown/CodeBlockPlaceholder.d.ts.map +1 -1
  249. package/lib/widgets/markdown/CodeBlockPlaceholder.js +1 -1
  250. package/lib/widgets/markdown/CodeBlockRendering.d.ts.map +1 -1
  251. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js +1 -1
  252. package/lib/widgets/markdown/remarkDirectiveHandler.d.ts.map +1 -1
  253. package/lib/widgets/markdown/textDiff.js.map +1 -1
  254. package/lib/widgets/monacoEditor/MonacoEditor.d.ts.map +1 -1
  255. package/lib/widgets/monacoEditor/MonacoEditor.js.map +1 -1
  256. package/lib/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
  257. package/lib/widgets/schema-editor/editor/PropertyEditor.js +1 -1
  258. package/lib/widgets/schema-editor/json-schema4-utils.js.map +1 -1
  259. package/lib/widgets/xml-viewer/components/Elements/index.js +1 -1
  260. package/lib/widgets/xml-viewer/components/Tag/index.js +1 -1
  261. package/lib/widgets/xml-viewer/constants/index.d.ts.map +1 -1
  262. package/lib/widgets/xml-viewer/helpers/index.d.ts +1 -1
  263. package/lib/widgets/xml-viewer/hooks/useCollapsible.d.ts +1 -1
  264. package/lib/widgets/xml-viewer/hooks/useCollapsible.d.ts.map +1 -1
  265. package/package.json +11 -11
  266. package/src/core/hooks/useFetch.ts +2 -1
  267. package/src/features/agent/chat/ModernAgentConversation.test.tsx +13 -0
  268. package/src/features/agent/chat/ModernAgentConversation.tsx +24 -3
  269. package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.test.ts +38 -0
  270. package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.tsx +26 -0
  271. package/src/features/agent/chat/ModernAgentOutput/SummaryConversation.ts +9 -0
  272. package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +21 -0
  273. package/src/features/agent/chat/hooks/useAgentStream.test.tsx +116 -1
  274. package/src/features/agent/chat/hooks/useAgentStream.ts +26 -0
  275. package/src/features/agent/chat/hooks/useFileProcessing.test.tsx +87 -0
  276. package/src/features/agent/chat/hooks/useFileProcessing.ts +32 -1
  277. package/src/features/store/collections/CollectionsTable.tsx +1 -1
  278. package/src/i18n/locales/ar.json +25 -0
  279. package/src/i18n/locales/de.json +25 -0
  280. package/src/i18n/locales/en.json +25 -0
  281. package/src/i18n/locales/es.json +25 -0
  282. package/src/i18n/locales/fr.json +25 -0
  283. package/src/i18n/locales/it.json +25 -0
  284. package/src/i18n/locales/ja.json +25 -0
  285. package/src/i18n/locales/ko.json +25 -0
  286. package/src/i18n/locales/pt.json +25 -0
  287. package/src/i18n/locales/ru.json +25 -0
  288. package/src/i18n/locales/tr.json +25 -0
  289. package/src/i18n/locales/zh-TW.json +25 -0
  290. package/src/i18n/locales/zh.json +25 -0
  291. package/src/session/UserSessionProvider.test.ts +69 -0
  292. package/src/session/UserSessionProvider.tsx +96 -60
  293. package/src/session/auth/composable.scope.test.ts +88 -0
  294. package/src/session/auth/composable.test.ts +205 -0
  295. package/src/session/auth/composable.ts +227 -58
  296. package/src/shell/login/SignInPrimitives.tsx +1 -0
  297. package/src/shell/login/SignInRecoveryStep.test.tsx +112 -0
  298. package/src/shell/login/SignInRecoveryStep.tsx +177 -0
  299. package/src/shell/login/SigninScreen.tsx +145 -27
@@ -1,7 +1,12 @@
1
1
  /**
2
2
  * Handle client caching and refresh of auth token
3
3
  */
4
- import { type AuthTokenPayload, RESTRICTED_ENVIRONMENT_ERROR_CODE } from '@vertesia/common';
4
+ import {
5
+ type AuthTokenPayload,
6
+ NO_ACCESSIBLE_ACCOUNT_ERROR_CODE,
7
+ REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,
8
+ RESTRICTED_ENVIRONMENT_ERROR_CODE,
9
+ } from '@vertesia/common';
5
10
  import { Env } from '@vertesia/ui/env';
6
11
  import { jwtDecode } from 'jwt-decode';
7
12
  import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from '../constants';
@@ -10,6 +15,27 @@ import { getFirebaseAuth, getFirebaseAuthToken } from './firebase';
10
15
  let AUTH_TOKEN_RAW: string | undefined;
11
16
  let AUTH_TOKEN: AuthTokenPayload | undefined;
12
17
 
18
+ function clearRejectedPersistedScope(accountId?: string, projectId?: string) {
19
+ if (!accountId) return;
20
+
21
+ const projectKey = `${LastSelectedProjectId_KEY}-${accountId}`;
22
+ if (projectId) {
23
+ const persistedProjectMatches = localStorage.getItem(projectKey) === projectId;
24
+ if (persistedProjectMatches) {
25
+ localStorage.removeItem(projectKey);
26
+ }
27
+ if (persistedProjectMatches && localStorage.getItem(LastSelectedAccountId_KEY) === accountId) {
28
+ localStorage.removeItem(LastSelectedAccountId_KEY);
29
+ }
30
+ return;
31
+ }
32
+
33
+ if (localStorage.getItem(LastSelectedAccountId_KEY) === accountId) {
34
+ localStorage.removeItem(LastSelectedAccountId_KEY);
35
+ localStorage.removeItem(projectKey);
36
+ }
37
+ }
38
+
13
39
  interface ComposableTokenResponse {
14
40
  rawToken: string;
15
41
  token: AuthTokenPayload;
@@ -17,6 +43,35 @@ interface ComposableTokenResponse {
17
43
  message?: string;
18
44
  }
19
45
 
46
+ export interface AuthenticatedIdentity {
47
+ email: string;
48
+ name?: string;
49
+ }
50
+
51
+ function identityFromAcceptedToken(token: string): AuthenticatedIdentity | undefined {
52
+ try {
53
+ const payload = jwtDecode<{ email?: unknown; name?: unknown }>(token);
54
+ if (typeof payload.email !== 'string' || !payload.email) return undefined;
55
+ return {
56
+ email: payload.email,
57
+ name: typeof payload.name === 'string' && payload.name ? payload.name : undefined,
58
+ };
59
+ } catch {
60
+ return undefined;
61
+ }
62
+ }
63
+
64
+ export function resolveAuthSelection(currentUrl: URL): { accountId?: string; projectId?: string } {
65
+ const urlAccount = currentUrl.searchParams.get('a') ?? undefined;
66
+ const urlProject = currentUrl.searchParams.get('p') ?? undefined;
67
+ const accountId =
68
+ urlAccount ??
69
+ (urlProject === undefined ? (localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined) : undefined);
70
+ const projectId = urlProject ?? localStorage.getItem(`${LastSelectedProjectId_KEY}-${accountId}`) ?? undefined;
71
+
72
+ return { accountId, projectId };
73
+ }
74
+
20
75
  function normalizeIssuer(value: string | undefined): string | undefined {
21
76
  return value?.replace(/\/+$/, '');
22
77
  }
@@ -114,7 +169,9 @@ export async function fetchComposableToken(
114
169
  error: error,
115
170
  },
116
171
  });
117
- throw new STSError('Failed to call STS endpoint', stsEndpoint);
172
+ throw new AuthenticationServiceError('The authentication service could not be reached', stsEndpoint, {
173
+ cause: error,
174
+ });
118
175
  });
119
176
 
120
177
  if (idToken && stsRes?.status === 404) {
@@ -211,15 +268,20 @@ export async function fetchComposableToken(
211
268
  throw new UserNotFoundError('User not found', idTokenDecoded.email);
212
269
  }
213
270
 
271
+ if (stsRes.status === 401) {
272
+ Env.logger.warn('STS rejected the identity credential', {
273
+ vertesia: { account_id: accountId, project_id: projectId, status: stsRes.status },
274
+ });
275
+ throw new CredentialError('Your sign-in credentials could not be verified', stsEndpoint, {
276
+ status: stsRes.status,
277
+ });
278
+ }
279
+
214
280
  if (stsRes.status === 403) {
215
- // Distinguish the "restricted environment" rejection (preview/preprod gated to
216
- // early-access users) from the account-access 403 handled below. The STS tags it
217
- // with a machine-readable business error code. Peek a clone so the body stays
218
- // readable for the fall-through path.
219
- const body = await stsRes
220
- .clone()
221
- .json()
222
- .catch(() => undefined);
281
+ const body = (await stsRes.json().catch(() => undefined)) as
282
+ | { errorCode?: string; message?: string }
283
+ | undefined;
284
+ const identity = identityFromAcceptedToken(idToken);
223
285
  if (body?.errorCode === RESTRICTED_ENVIRONMENT_ERROR_CODE) {
224
286
  Env.logger.warn('403: User lacks early-access for this restricted environment', {
225
287
  vertesia: {
@@ -232,44 +294,46 @@ export async function fetchComposableToken(
232
294
  );
233
295
  }
234
296
 
235
- // User doesn't have access to the requested account/project, or has no accounts
236
- // This can happen with:
237
- // 1. Stale localStorage from previous user
238
- // 2. User invited to a new account (doesn't have access yet)
239
- // 3. User exists but has no accounts at all
297
+ if (body?.errorCode === NO_ACCESSIBLE_ACCOUNT_ERROR_CODE) {
298
+ Env.logger.warn('STS identity has no accessible account', {
299
+ vertesia: { error_code: body.errorCode, status: stsRes.status },
300
+ });
301
+ throw new NoAccessibleAccountError(stsEndpoint, body.message, identity);
302
+ }
240
303
 
241
- if (retryCount > 0) {
242
- // Already retried without account scope - this is a real authorization failure
243
- console.error('403: Access denied even without account scope - user may have no accounts');
244
- Env.logger.error('403: Access denied after retry - authorization failure', {
304
+ // During rolling deployment an older STS may return an uncoded user-token 403.
305
+ // Treat it as a requested-scope rejection; all calls in this function issue user tokens.
306
+ if (!body?.errorCode || body.errorCode === REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE) {
307
+ Env.logger.warn('STS rejected the requested account or project', {
245
308
  vertesia: {
246
309
  account_id: accountId,
247
310
  project_id: projectId,
248
311
  status: stsRes.status,
249
- retry_count: retryCount,
312
+ error_code: body?.errorCode ?? REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,
250
313
  },
251
314
  });
252
- throw new Error('Access denied - user may not have access to any accounts');
315
+ throw new RequestedScopeUnavailableError(
316
+ stsEndpoint,
317
+ accountId,
318
+ projectId,
319
+ body?.errorCode ? body.message : undefined,
320
+ identity,
321
+ );
253
322
  }
254
323
 
255
- console.log('403: Access denied - clearing cached account and retrying without account scope');
256
- Env.logger.warn('403: Access denied - clearing cached account and retrying', {
324
+ Env.logger.error('STS returned an unrecognized forbidden response', {
257
325
  vertesia: {
258
326
  account_id: accountId,
259
327
  project_id: projectId,
260
328
  status: stsRes.status,
261
- retry_count: retryCount,
329
+ error_code: body.errorCode,
262
330
  },
263
331
  });
264
-
265
- // Clear any stale account/project from localStorage
266
- localStorage.removeItem(LastSelectedAccountId_KEY);
267
- if (accountId) {
268
- localStorage.removeItem(`${LastSelectedProjectId_KEY}-${accountId}`);
269
- }
270
-
271
- // Retry without account/project scope - let user log in to their default account
272
- return fetchComposableToken(getIdToken, undefined, undefined, ttl, retryCount + 1);
332
+ throw new AuthenticationServiceError('The authentication service rejected the request', stsEndpoint, {
333
+ status: stsRes.status,
334
+ errorCode: body.errorCode,
335
+ cause: body,
336
+ });
273
337
  }
274
338
 
275
339
  if (!stsRes.ok) {
@@ -283,7 +347,10 @@ export async function fetchComposableToken(
283
347
  project_id: projectId,
284
348
  },
285
349
  });
286
- throw new Error(`Failed to get token from STS: ${stsRes.status}`);
350
+ throw new AuthenticationServiceError('The authentication service could not complete sign-in', stsEndpoint, {
351
+ status: stsRes.status,
352
+ cause: errorText,
353
+ });
287
354
  }
288
355
 
289
356
  const { token } = await stsRes.json();
@@ -300,11 +367,7 @@ export async function fetchComposableToken(
300
367
  throw error;
301
368
  }
302
369
 
303
- // Clear any stale account/project from localStorage on error
304
- localStorage.removeItem(LastSelectedAccountId_KEY);
305
- if (accountId) {
306
- localStorage.removeItem(`${LastSelectedProjectId_KEY}-${accountId}`);
307
- }
370
+ if (error instanceof Error && error.message === 'Customer-domain user requires an invite to join') throw error;
308
371
  console.error('Failed to get composable token from STS', error);
309
372
  Env.logger.error('Failed to get composable token from STS', {
310
373
  vertesia: {
@@ -313,7 +376,9 @@ export async function fetchComposableToken(
313
376
  error: error,
314
377
  },
315
378
  });
316
- throw new Error('Failed to get composable token', { cause: error });
379
+ throw new AuthenticationServiceError('The authentication service could not complete sign-in', stsEndpoint, {
380
+ cause: error,
381
+ });
317
382
  }
318
383
  }
319
384
 
@@ -354,14 +419,26 @@ export async function getComposableToken(
354
419
  forceRefresh = false,
355
420
  useInternalAuth = false,
356
421
  ): Promise<ComposableTokenResponse> {
357
- const selectedAccount = accountId ?? localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined;
422
+ const selectedAccount =
423
+ accountId ??
424
+ (projectId === undefined ? (localStorage.getItem(LastSelectedAccountId_KEY) ?? undefined) : undefined);
358
425
  const selectedProject =
359
426
  projectId ?? localStorage.getItem(`${LastSelectedProjectId_KEY}-${selectedAccount}`) ?? undefined;
360
427
  const devAuthToken = Env.isLocalDev ? Env.devAuthToken : undefined;
361
428
  const suppliedToken = devAuthToken ?? initToken ?? AUTH_TOKEN_RAW;
362
429
 
363
- //token is still valid for more than 5 minutes
364
- if (!forceRefresh && AUTH_TOKEN_RAW && AUTH_TOKEN && AUTH_TOKEN.exp > Date.now() / 1000 + 300) {
430
+ const cachedTokenMatchesScope =
431
+ (!selectedAccount || AUTH_TOKEN?.account?.id === selectedAccount) &&
432
+ (!selectedProject || AUTH_TOKEN?.project?.id === selectedProject);
433
+
434
+ // Token is still valid for more than 5 minutes and belongs to the requested scope.
435
+ if (
436
+ !forceRefresh &&
437
+ AUTH_TOKEN_RAW &&
438
+ AUTH_TOKEN &&
439
+ cachedTokenMatchesScope &&
440
+ AUTH_TOKEN.exp > Date.now() / 1000 + 300
441
+ ) {
365
442
  return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };
366
443
  }
367
444
 
@@ -378,19 +455,34 @@ export async function getComposableToken(
378
455
  return { rawToken: AUTH_TOKEN_RAW, token: AUTH_TOKEN, error: false };
379
456
  }
380
457
 
381
- //token is close to expire, refresh it
382
- if (!devAuthToken && !useInternalAuth && getFirebaseAuth().currentUser) {
383
- //we have a firebase user, get the token from there
384
- AUTH_TOKEN_RAW = await fetchComposableTokenFromFirebaseToken(selectedAccount, selectedProject);
385
- } else if (!devAuthToken && (initToken || AUTH_TOKEN_RAW)) {
386
- // we have a token already and no firebase user, refresh it
387
- AUTH_TOKEN_RAW = await fetchComposableToken(
388
- () => Promise.resolve(initToken ?? AUTH_TOKEN_RAW),
389
- selectedAccount,
390
- selectedProject,
391
- );
392
- } else if (devAuthToken) {
393
- AUTH_TOKEN_RAW = devAuthToken;
458
+ try {
459
+ //token is close to expire, refresh it
460
+ if (!devAuthToken && !useInternalAuth && getFirebaseAuth().currentUser) {
461
+ //we have a firebase user, get the token from there
462
+ AUTH_TOKEN_RAW = await fetchComposableTokenFromFirebaseToken(selectedAccount, selectedProject);
463
+ } else if (!devAuthToken && (initToken || AUTH_TOKEN_RAW)) {
464
+ // we have a token already and no firebase user, refresh it
465
+ AUTH_TOKEN_RAW = await fetchComposableToken(
466
+ () => Promise.resolve(initToken ?? AUTH_TOKEN_RAW),
467
+ selectedAccount,
468
+ selectedProject,
469
+ );
470
+ } else if (devAuthToken) {
471
+ AUTH_TOKEN_RAW = devAuthToken;
472
+ }
473
+ } catch (error: unknown) {
474
+ if (
475
+ error instanceof RequestedScopeUnavailableError ||
476
+ error instanceof NoAccessibleAccountError ||
477
+ error instanceof CredentialError
478
+ ) {
479
+ AUTH_TOKEN_RAW = undefined;
480
+ AUTH_TOKEN = undefined;
481
+ if (error instanceof RequestedScopeUnavailableError) {
482
+ clearRejectedPersistedScope(selectedAccount, selectedProject);
483
+ }
484
+ }
485
+ throw error;
394
486
  }
395
487
 
396
488
  if (!AUTH_TOKEN_RAW) {
@@ -430,13 +522,90 @@ export class UserNotFoundError extends Error {
430
522
 
431
523
  export class STSError extends Error {
432
524
  stsURL: string;
433
- constructor(message: string, stsURL: string) {
434
- super(message);
525
+ readonly status?: number;
526
+ readonly errorCode?: string;
527
+ constructor(message: string, stsURL: string, options?: ErrorOptions & { status?: number; errorCode?: string }) {
528
+ super(message, options);
435
529
  this.name = 'STSError';
436
530
  this.stsURL = stsURL;
531
+ this.status = options?.status;
532
+ this.errorCode = options?.errorCode;
533
+ }
534
+ }
535
+
536
+ export type RequestedScopeKind = 'account' | 'project';
537
+ export type AuthSelectorSource = 'url' | 'persisted';
538
+
539
+ export class RequestedScopeUnavailableError extends STSError {
540
+ requestedScope: RequestedScopeKind;
541
+ selectorSource?: AuthSelectorSource;
542
+ recoveryUrl?: string;
543
+
544
+ constructor(
545
+ stsURL: string,
546
+ public readonly accountId?: string,
547
+ public readonly projectId?: string,
548
+ responseMessage?: string,
549
+ public readonly identity?: AuthenticatedIdentity,
550
+ ) {
551
+ super(responseMessage ?? 'The requested account or project is not available.', stsURL, {
552
+ status: 403,
553
+ errorCode: REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE,
554
+ });
555
+ this.name = 'RequestedScopeUnavailableError';
556
+ this.requestedScope = projectId ? 'project' : 'account';
557
+ }
558
+ }
559
+
560
+ /** @deprecated Use RequestedScopeUnavailableError. */
561
+ export const TokenAuthorizationError = RequestedScopeUnavailableError;
562
+ /** @deprecated Use RequestedScopeUnavailableError. */
563
+ export type TokenAuthorizationError = RequestedScopeUnavailableError;
564
+
565
+ export class NoAccessibleAccountError extends STSError {
566
+ constructor(
567
+ stsURL: string,
568
+ responseMessage?: string,
569
+ public readonly identity?: AuthenticatedIdentity,
570
+ ) {
571
+ super(responseMessage ?? 'No accessible account is available for this user.', stsURL, {
572
+ status: 403,
573
+ errorCode: NO_ACCESSIBLE_ACCOUNT_ERROR_CODE,
574
+ });
575
+ this.name = 'NoAccessibleAccountError';
437
576
  }
438
577
  }
439
578
 
579
+ export class CredentialError extends STSError {
580
+ constructor(message: string, stsURL: string, options?: ErrorOptions & { status?: number; errorCode?: string }) {
581
+ super(message, stsURL, options);
582
+ this.name = 'CredentialError';
583
+ }
584
+ }
585
+
586
+ export class AuthenticationServiceError extends STSError {
587
+ constructor(message: string, stsURL: string, options?: ErrorOptions & { status?: number; errorCode?: string }) {
588
+ super(message, stsURL, options);
589
+ this.name = 'AuthenticationServiceError';
590
+ }
591
+ }
592
+
593
+ export function isRequestedScopeUnavailableError(error: unknown): error is RequestedScopeUnavailableError {
594
+ return error instanceof RequestedScopeUnavailableError;
595
+ }
596
+
597
+ export function isNoAccessibleAccountError(error: unknown): error is NoAccessibleAccountError {
598
+ return error instanceof NoAccessibleAccountError;
599
+ }
600
+
601
+ export function isCredentialError(error: unknown): error is CredentialError {
602
+ return error instanceof CredentialError;
603
+ }
604
+
605
+ export function isAuthenticationServiceError(error: unknown): error is AuthenticationServiceError {
606
+ return error instanceof AuthenticationServiceError;
607
+ }
608
+
440
609
  /**
441
610
  * Thrown when the STS rejects sign-in because the current environment (preview/preprod) is
442
611
  * restricted to early-access users and this user lacks the `early-access` annotation. The
@@ -65,6 +65,7 @@ const SIGNIN_STEP_BUTTON_VARIANTS = {
65
65
 
66
66
  interface SignInStepButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
67
67
  variant?: keyof typeof SIGNIN_STEP_BUTTON_VARIANTS;
68
+ ref?: Ref<HTMLButtonElement>;
68
69
  }
69
70
 
70
71
  /** Button used across the sign-in steps. */
@@ -0,0 +1,112 @@
1
+ import { fireEvent, render, screen } from '@testing-library/react';
2
+ import { afterEach, describe, expect, it, vi } from 'vitest';
3
+ import SignInRecoveryStep from './SignInRecoveryStep';
4
+
5
+ const translations: Record<string, string> = {
6
+ 'auth.recovery.scopeProject.eyebrow': 'Link Unavailable',
7
+ 'auth.recovery.scopeProject.title': 'Project not available',
8
+ 'auth.recovery.scopeProject.body':
9
+ 'You’re signed in, but the project in this link isn’t available. The link may be outdated, or you may not have access.',
10
+ 'auth.recovery.scopeAccount.eyebrow': 'Link Unavailable',
11
+ 'auth.recovery.scopeAccount.title': 'Account not available',
12
+ 'auth.recovery.scopeAccount.body': 'Account unavailable body',
13
+ 'auth.recovery.noAccessibleAccount.eyebrow': 'Account Required',
14
+ 'auth.recovery.noAccessibleAccount.title': 'No account is available',
15
+ 'auth.recovery.noAccessibleAccount.body': 'No accessible account body',
16
+ 'auth.recovery.credential.eyebrow': 'Sign-In Required',
17
+ 'auth.recovery.credential.title': 'Your sign-in could not be verified',
18
+ 'auth.recovery.credential.body': 'Credential body',
19
+ 'auth.recovery.service.eyebrow': 'Service Unavailable',
20
+ 'auth.recovery.service.title': "We couldn't complete sign-in",
21
+ 'auth.recovery.service.body': 'Service body',
22
+ 'auth.recovery.continue': 'Continue to Vertesia',
23
+ 'auth.recovery.tryAgain': 'Try again',
24
+ 'auth.recovery.useDifferentAccount': 'Use a different account',
25
+ 'auth.recovery.supportPrefix': 'If you need help, contact',
26
+ 'auth.recovery.technicalDetails': 'Technical details',
27
+ 'auth.recovery.errorCode': 'Error code',
28
+ 'auth.recovery.httpStatus': 'HTTP status',
29
+ 'auth.recovery.requestedAccount': 'Requested account',
30
+ 'auth.recovery.requestedProject': 'Requested project',
31
+ 'auth.recovery.errorMessage': 'Error message',
32
+ 'auth.returning.notYou': 'Not you?',
33
+ };
34
+
35
+ vi.mock('@vertesia/ui/i18n', () => ({
36
+ useUITranslation: () => ({ t: (key: string) => translations[key] ?? key }),
37
+ }));
38
+
39
+ describe('SignInRecoveryStep', () => {
40
+ afterEach(() => {
41
+ document.body.innerHTML = '';
42
+ });
43
+
44
+ it('shows project-safe copy, focuses the primary action, and prevents duplicate submissions', () => {
45
+ const onContinue = vi.fn();
46
+ render(<SignInRecoveryStep kind="scopeProject" onContinue={onContinue} onUseDifferentAccount={vi.fn()} />);
47
+
48
+ expect(screen.getByText('Project not available')).toBeTruthy();
49
+ expect(screen.getByText(/project in this link isn’t available/)).toBeTruthy();
50
+ const continueButton = screen.getByRole('button', { name: 'Continue to Vertesia' });
51
+ expect(document.activeElement).toBe(continueButton);
52
+
53
+ fireEvent.click(continueButton);
54
+ fireEvent.click(continueButton);
55
+
56
+ expect(onContinue).toHaveBeenCalledOnce();
57
+ expect((continueButton as HTMLButtonElement).disabled).toBe(true);
58
+ });
59
+
60
+ it('omits continue for an identity with no accessible account and includes support guidance', () => {
61
+ const onUseDifferentAccount = vi.fn();
62
+ render(<SignInRecoveryStep kind="noAccessibleAccount" onUseDifferentAccount={onUseDifferentAccount} />);
63
+
64
+ expect(screen.queryByRole('button', { name: 'Continue to Vertesia' })).toBeNull();
65
+ expect(screen.getByText('No account is available')).toBeTruthy();
66
+ expect(screen.getByRole('link', { name: 'support@vertesiahq.com' }).getAttribute('href')).toBe(
67
+ 'mailto:support@vertesiahq.com',
68
+ );
69
+ expect(document.activeElement).toBe(screen.getByRole('button', { name: 'Use a different account' }));
70
+ });
71
+
72
+ it('shows submitted selectors and the normalized server error in collapsed technical details', () => {
73
+ render(
74
+ <SignInRecoveryStep
75
+ details={{
76
+ accountId: 'apple',
77
+ errorCode: 'requested_scope_unavailable',
78
+ message: 'The requested account or project is not available.',
79
+ projectId: 'banana',
80
+ status: 403,
81
+ }}
82
+ kind="scopeProject"
83
+ onContinue={vi.fn()}
84
+ onUseDifferentAccount={vi.fn()}
85
+ />,
86
+ );
87
+
88
+ const disclosure = screen.getByText('Technical details').closest('details');
89
+ expect(disclosure?.hasAttribute('open')).toBe(false);
90
+ expect(screen.getByText('requested_scope_unavailable')).toBeTruthy();
91
+ expect(screen.getByText('apple')).toBeTruthy();
92
+ expect(screen.getByText('banana')).toBeTruthy();
93
+ expect(screen.getByText('The requested account or project is not available.')).toBeTruthy();
94
+ });
95
+
96
+ it('reuses the returning-user identity row for the authenticated credential', () => {
97
+ const onUseDifferentAccount = vi.fn();
98
+ render(
99
+ <SignInRecoveryStep
100
+ identity={{ email: 'leon@example.com', name: 'Leon Ruggiero' }}
101
+ kind="scopeProject"
102
+ onContinue={vi.fn()}
103
+ onUseDifferentAccount={onUseDifferentAccount}
104
+ />,
105
+ );
106
+
107
+ expect(screen.getByText('Leon Ruggiero')).toBeTruthy();
108
+ expect(screen.getByText('leon@example.com')).toBeTruthy();
109
+ fireEvent.click(screen.getByRole('button', { name: 'Not you?' }));
110
+ expect(onUseDifferentAccount).toHaveBeenCalledOnce();
111
+ });
112
+ });