@vertesia/ui 1.5.0-dev.20260714.072725Z → 1.5.0-dev.20260722.120446Z
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/core/components/SidePanel.js +1 -1
- package/lib/core/components/SidePanel.js.map +1 -1
- package/lib/core/components/shadcn/button.d.ts +10 -3
- package/lib/core/components/shadcn/button.d.ts.map +1 -1
- package/lib/core/components/shadcn/button.js +41 -9
- package/lib/core/components/shadcn/button.js.map +1 -1
- package/lib/core/components/shadcn/modal/dialog.js +1 -1
- package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
- package/lib/core/components/shadcn/theme/ThemeSwitcher.js +1 -1
- package/lib/core/components/shadcn/theme/ThemeSwitcher.js.map +1 -1
- package/lib/env/index.d.ts +19 -0
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js +6 -0
- package/lib/env/index.js.map +1 -1
- package/lib/features/agent/chat/AgentChatPlaybackControls.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentChatPlaybackControls.js +2 -2
- package/lib/features/agent/chat/AgentChatPlaybackControls.js.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.js +2 -2
- package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
- package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
- package/lib/features/agent/chat/ArtifactsTab.js +68 -11
- package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
- package/lib/features/agent/chat/ImageLightbox.d.ts.map +1 -1
- package/lib/features/agent/chat/ImageLightbox.js +1 -1
- package/lib/features/agent/chat/ImageLightbox.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +52 -49
- package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +5 -5
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/Header.js +4 -2
- package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +2 -2
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +2 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts +6 -0
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js +15 -1
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js.map +1 -1
- package/lib/features/agent/chat/hooks/useArtifacts.d.ts +8 -1
- package/lib/features/agent/chat/hooks/useArtifacts.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useArtifacts.js +28 -10
- package/lib/features/agent/chat/hooks/useArtifacts.js.map +1 -1
- package/lib/features/document-viewer/UniversalDocumentViewer.d.ts +73 -0
- package/lib/features/document-viewer/UniversalDocumentViewer.d.ts.map +1 -0
- package/lib/features/document-viewer/UniversalDocumentViewer.js +415 -0
- package/lib/features/document-viewer/UniversalDocumentViewer.js.map +1 -0
- package/lib/features/document-viewer/index.d.ts +2 -0
- package/lib/features/document-viewer/index.d.ts.map +1 -0
- package/lib/features/document-viewer/index.js +2 -0
- package/lib/features/document-viewer/index.js.map +1 -0
- package/lib/features/environment/SelectEnvironment.d.ts +28 -0
- package/lib/features/environment/SelectEnvironment.d.ts.map +1 -0
- package/lib/features/environment/SelectEnvironment.js +97 -0
- package/lib/features/environment/SelectEnvironment.js.map +1 -0
- package/lib/features/environment/index.d.ts +2 -0
- package/lib/features/environment/index.d.ts.map +1 -0
- package/lib/features/environment/index.js +2 -0
- package/lib/features/environment/index.js.map +1 -0
- package/lib/features/index.d.ts +3 -0
- package/lib/features/index.d.ts.map +1 -1
- package/lib/features/index.js +3 -0
- package/lib/features/index.js.map +1 -1
- package/lib/features/magic-pdf/AnnotatedImageSlider.js +12 -12
- package/lib/features/magic-pdf/AnnotatedImageSlider.js.map +1 -1
- package/lib/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
- package/lib/features/magic-pdf/DownloadPopover.js +3 -17
- package/lib/features/magic-pdf/DownloadPopover.js.map +1 -1
- package/lib/features/magic-pdf/ExtractedContentView.js +2 -6
- package/lib/features/magic-pdf/ExtractedContentView.js.map +1 -1
- package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.d.ts +25 -0
- package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.d.ts.map +1 -0
- package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.js +42 -0
- package/lib/features/magic-pdf/GroundedExtractionAssistantPanel.js.map +1 -0
- package/lib/features/magic-pdf/GroundedExtractionView.d.ts +27 -0
- package/lib/features/magic-pdf/GroundedExtractionView.d.ts.map +1 -0
- package/lib/features/magic-pdf/GroundedExtractionView.js +377 -0
- package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -0
- package/lib/features/magic-pdf/MagicPdfProvider.d.ts +0 -2
- package/lib/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -1
- package/lib/features/magic-pdf/MagicPdfProvider.js +15 -42
- package/lib/features/magic-pdf/MagicPdfProvider.js.map +1 -1
- package/lib/features/magic-pdf/MagicPdfView.d.ts.map +1 -1
- package/lib/features/magic-pdf/MagicPdfView.js +2 -19
- package/lib/features/magic-pdf/MagicPdfView.js.map +1 -1
- package/lib/features/magic-pdf/index.d.ts +1 -0
- package/lib/features/magic-pdf/index.d.ts.map +1 -1
- package/lib/features/magic-pdf/index.js +1 -0
- package/lib/features/magic-pdf/index.js.map +1 -1
- package/lib/features/magic-pdf/types.d.ts +1 -1
- package/lib/features/magic-pdf/types.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.js +2 -2
- package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.js +1 -1
- package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
- package/lib/features/oauth/RemoteMcpConnectionButton.d.ts.map +1 -1
- package/lib/features/oauth/RemoteMcpConnectionButton.js +1 -1
- package/lib/features/oauth/RemoteMcpConnectionButton.js.map +1 -1
- package/lib/features/pdf-viewer/PdfPageSlider.js +2 -2
- package/lib/features/pdf-viewer/PdfPageSlider.js.map +1 -1
- package/lib/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -1
- package/lib/features/pdf-viewer/SimplePdfViewer.js +3 -3
- package/lib/features/pdf-viewer/SimplePdfViewer.js.map +1 -1
- package/lib/features/permissions/UserPermissionsProvider.d.ts +2 -3
- package/lib/features/permissions/UserPermissionsProvider.d.ts.map +1 -1
- package/lib/features/permissions/UserPermissionsProvider.js +31 -38
- package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
- package/lib/features/store/objects/DocumentQuickFilter.d.ts.map +1 -1
- package/lib/features/store/objects/DocumentQuickFilter.js +5 -5
- package/lib/features/store/objects/DocumentQuickFilter.js.map +1 -1
- package/lib/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
- package/lib/features/store/objects/DocumentSearchResults.js +2 -2
- package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
- package/lib/features/store/objects/components/ContentDispositionButton.d.ts.map +1 -1
- package/lib/features/store/objects/components/ContentDispositionButton.js +1 -1
- package/lib/features/store/objects/components/ContentDispositionButton.js.map +1 -1
- package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
- package/lib/features/store/objects/components/ContentOverview.js +148 -31
- package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
- package/lib/features/store/objects/components/DocumentIcon.js +1 -1
- package/lib/features/store/objects/components/VectorSearchWidget.js +1 -1
- package/lib/features/store/objects/components/VectorSearchWidget.js.map +1 -1
- package/lib/features/store/objects/components/useContentPanelHooks.d.ts +1 -2
- package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
- package/lib/features/store/objects/components/useContentPanelHooks.js +4 -8
- package/lib/features/store/objects/components/useContentPanelHooks.js.map +1 -1
- package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
- package/lib/features/store/objects/layout/renderers.js +2 -2
- package/lib/features/store/objects/layout/renderers.js.map +1 -1
- package/lib/features/store/objects/selection/SelectionActions.js +1 -1
- package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
- package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
- package/lib/features/store/types/ContentObjectTypesTable.js +4 -4
- package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
- package/lib/features/store/types/IntakePolicyEditor.d.ts +15 -0
- package/lib/features/store/types/IntakePolicyEditor.d.ts.map +1 -0
- package/lib/features/store/types/IntakePolicyEditor.js +455 -0
- package/lib/features/store/types/IntakePolicyEditor.js.map +1 -0
- package/lib/features/store/types/IntakePolicyForm.d.ts +11 -0
- package/lib/features/store/types/IntakePolicyForm.d.ts.map +1 -0
- package/lib/features/store/types/IntakePolicyForm.js +91 -0
- package/lib/features/store/types/IntakePolicyForm.js.map +1 -0
- package/lib/features/store/types/SelectContentTypeModal.js +1 -1
- package/lib/features/store/types/SelectContentTypeModal.js.map +1 -1
- package/lib/features/store/types/index.d.ts +2 -0
- package/lib/features/store/types/index.d.ts.map +1 -1
- package/lib/features/store/types/index.js +2 -0
- package/lib/features/store/types/index.js.map +1 -1
- package/lib/features/store/types/intake-policy-editor.logic.d.ts +7 -0
- package/lib/features/store/types/intake-policy-editor.logic.d.ts.map +1 -0
- package/lib/features/store/types/intake-policy-editor.logic.js +60 -0
- package/lib/features/store/types/intake-policy-editor.logic.js.map +1 -0
- package/lib/features/store/types/typeCatalogCache.d.ts +5 -0
- package/lib/features/store/types/typeCatalogCache.d.ts.map +1 -0
- package/lib/features/store/types/typeCatalogCache.js +40 -0
- package/lib/features/store/types/typeCatalogCache.js.map +1 -0
- package/lib/features/user/UserInfo.js +7 -3
- package/lib/features/user/UserInfo.js.map +1 -1
- package/lib/features/views/ViewExperience.d.ts +20 -0
- package/lib/features/views/ViewExperience.d.ts.map +1 -0
- package/lib/features/views/ViewExperience.js +188 -0
- package/lib/features/views/ViewExperience.js.map +1 -0
- package/lib/features/views/ViewNavigation.d.ts +3 -0
- package/lib/features/views/ViewNavigation.d.ts.map +1 -0
- package/lib/features/views/ViewNavigation.js +36 -0
- package/lib/features/views/ViewNavigation.js.map +1 -0
- package/lib/features/views/ViewResults.d.ts +9 -0
- package/lib/features/views/ViewResults.d.ts.map +1 -0
- package/lib/features/views/ViewResults.js +229 -0
- package/lib/features/views/ViewResults.js.map +1 -0
- package/lib/features/views/ViewSearch.d.ts +3 -0
- package/lib/features/views/ViewSearch.d.ts.map +1 -0
- package/lib/features/views/ViewSearch.js +74 -0
- package/lib/features/views/ViewSearch.js.map +1 -0
- package/lib/features/views/index.d.ts +7 -0
- package/lib/features/views/index.d.ts.map +1 -0
- package/lib/features/views/index.js +7 -0
- package/lib/features/views/index.js.map +1 -0
- package/lib/features/views/types.d.ts +39 -0
- package/lib/features/views/types.d.ts.map +1 -0
- package/lib/features/views/types.js +2 -0
- package/lib/features/views/types.js.map +1 -0
- package/lib/features/views/viewState.d.ts +14 -0
- package/lib/features/views/viewState.d.ts.map +1 -0
- package/lib/features/views/viewState.js +117 -0
- package/lib/features/views/viewState.js.map +1 -0
- package/lib/i18n/locales/ar.json +18 -0
- package/lib/i18n/locales/de.json +14 -0
- package/lib/i18n/locales/en.json +246 -32
- package/lib/i18n/locales/es.json +15 -0
- package/lib/i18n/locales/fr.json +15 -0
- package/lib/i18n/locales/it.json +15 -0
- package/lib/i18n/locales/ja.json +13 -0
- package/lib/i18n/locales/ko.json +13 -0
- package/lib/i18n/locales/pt.json +15 -0
- package/lib/i18n/locales/ru.json +16 -0
- package/lib/i18n/locales/tr.json +14 -0
- package/lib/i18n/locales/zh-TW.json +13 -0
- package/lib/i18n/locales/zh.json +13 -0
- package/lib/router/HistoryNavigator.d.ts.map +1 -1
- package/lib/router/HistoryNavigator.js +27 -14
- package/lib/router/HistoryNavigator.js.map +1 -1
- package/lib/router/Nav.d.ts.map +1 -1
- package/lib/router/Nav.js +21 -6
- package/lib/router/Nav.js.map +1 -1
- package/lib/router/Router.d.ts.map +1 -1
- package/lib/router/Router.js +19 -4
- package/lib/router/Router.js.map +1 -1
- package/lib/router/path.d.ts +15 -0
- package/lib/router/path.d.ts.map +1 -1
- package/lib/router/path.js +43 -0
- package/lib/router/path.js.map +1 -1
- package/lib/session/UserSession.d.ts +6 -0
- package/lib/session/UserSession.d.ts.map +1 -1
- package/lib/session/UserSession.js +36 -12
- package/lib/session/UserSession.js.map +1 -1
- package/lib/session/UserSessionProvider.d.ts.map +1 -1
- package/lib/session/UserSessionProvider.js +111 -49
- package/lib/session/UserSessionProvider.js.map +1 -1
- package/lib/session/auth/composable.d.ts.map +1 -1
- package/lib/session/auth/composable.js +52 -3
- package/lib/session/auth/composable.js.map +1 -1
- package/lib/session/auth/domainRouting.d.ts +25 -0
- package/lib/session/auth/domainRouting.d.ts.map +1 -1
- package/lib/session/auth/domainRouting.js +36 -0
- package/lib/session/auth/domainRouting.js.map +1 -1
- package/lib/shell/login/SignInRestrictedEnvStep.d.ts +1 -1
- package/lib/shell/login/SignInRestrictedEnvStep.js +1 -1
- package/lib/shell/login/SigninScreen.js +9 -1
- package/lib/shell/login/SigninScreen.js.map +1 -1
- package/lib/shell/login/productionUrl.d.ts +0 -14
- package/lib/shell/login/productionUrl.d.ts.map +1 -1
- package/lib/shell/login/productionUrl.js +14 -5
- package/lib/shell/login/productionUrl.js.map +1 -1
- package/lib/vertesia-ui-core.js +4 -4
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-env.js +1 -1
- package/lib/vertesia-ui-env.js.map +1 -1
- package/lib/vertesia-ui-features.js +35 -28
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.js +15 -15
- package/lib/vertesia-ui-i18n.js.map +1 -1
- package/lib/vertesia-ui-router.js +1 -1
- package/lib/vertesia-ui-router.js.map +1 -1
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/lib/vertesia-ui-shell.js +1 -1
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +7 -7
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/lib/widgets/form/Form.js +1 -1
- package/lib/widgets/form/Form.js.map +1 -1
- package/lib/widgets/json-view/JSONSwitcher.js +1 -1
- package/lib/widgets/json-view/JSONSwitcher.js.map +1 -1
- package/lib/widgets/json-view/JSONView.js +6 -6
- package/lib/widgets/json-view/JSONView.js.map +1 -1
- package/lib/widgets/monacoEditor/MonacoEditor.d.ts.map +1 -1
- package/lib/widgets/monacoEditor/MonacoEditor.js +29 -0
- package/lib/widgets/monacoEditor/MonacoEditor.js.map +1 -1
- package/lib/widgets/schema-editor/ManagedSchema.d.ts +8 -0
- package/lib/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
- package/lib/widgets/schema-editor/ManagedSchema.js +16 -0
- package/lib/widgets/schema-editor/ManagedSchema.js.map +1 -1
- package/lib/widgets/schema-editor/editor/EditableSchemaProperty.d.ts +2 -0
- package/lib/widgets/schema-editor/editor/EditableSchemaProperty.d.ts.map +1 -1
- package/lib/widgets/schema-editor/editor/EditableSchemaProperty.js +1 -0
- package/lib/widgets/schema-editor/editor/EditableSchemaProperty.js.map +1 -1
- package/lib/widgets/schema-editor/editor/PropertyViewer.d.ts.map +1 -1
- package/lib/widgets/schema-editor/editor/PropertyViewer.js +4 -1
- package/lib/widgets/schema-editor/editor/PropertyViewer.js.map +1 -1
- package/lib/widgets/schema-editor/editor/SchemaEditor.d.ts.map +1 -1
- package/lib/widgets/schema-editor/editor/SchemaEditor.js +22 -11
- package/lib/widgets/schema-editor/editor/SchemaEditor.js.map +1 -1
- package/llms.txt +70 -70
- package/package.json +33 -32
- package/src/__tests__/a11y.test.tsx +73 -0
- package/src/core/components/SidePanel.tsx +1 -1
- package/src/core/components/shadcn/button.tsx +67 -12
- package/src/core/components/shadcn/modal/dialog.tsx +1 -1
- package/src/core/components/shadcn/theme/ThemeSwitcher.tsx +1 -1
- package/src/env/index.ts +24 -0
- package/src/features/agent/chat/AgentChatPlaybackControls.tsx +0 -5
- package/src/features/agent/chat/AgentRequestInputOverlay.test.tsx +31 -0
- package/src/features/agent/chat/AgentRequestInputOverlay.tsx +6 -2
- package/src/features/agent/chat/ArtifactsTab.tsx +165 -21
- package/src/features/agent/chat/ImageLightbox.tsx +0 -1
- package/src/features/agent/chat/ModernAgentConversation.test.tsx +48 -0
- package/src/features/agent/chat/ModernAgentConversation.tsx +53 -51
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +6 -1
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +21 -15
- package/src/features/agent/chat/ModernAgentOutput/Header.tsx +3 -3
- package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +0 -2
- package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +4 -1
- package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.test.ts +80 -0
- package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.ts +21 -1
- package/src/features/agent/chat/hooks/useArtifacts.ts +44 -11
- package/src/features/document-viewer/UniversalDocumentViewer.test.tsx +123 -0
- package/src/features/document-viewer/UniversalDocumentViewer.tsx +746 -0
- package/src/features/document-viewer/index.ts +1 -0
- package/src/features/environment/SelectEnvironment.tsx +186 -0
- package/src/features/environment/index.ts +1 -0
- package/src/features/index.ts +3 -0
- package/src/features/magic-pdf/AnnotatedImageSlider.tsx +62 -66
- package/src/features/magic-pdf/DownloadPopover.tsx +5 -48
- package/src/features/magic-pdf/ExtractedContentView.tsx +2 -14
- package/src/features/magic-pdf/GroundedExtractionAssistantPanel.tsx +86 -0
- package/src/features/magic-pdf/GroundedExtractionView.test.tsx +65 -0
- package/src/features/magic-pdf/GroundedExtractionView.tsx +1055 -0
- package/src/features/magic-pdf/MagicPdfProvider.tsx +17 -47
- package/src/features/magic-pdf/MagicPdfView.tsx +3 -51
- package/src/features/magic-pdf/index.ts +1 -0
- package/src/features/magic-pdf/types.ts +1 -1
- package/src/features/oauth/McpConnectionsButton.tsx +0 -2
- package/src/features/oauth/McpConnectionsDialog.tsx +0 -1
- package/src/features/oauth/RemoteMcpConnectionButton.tsx +0 -1
- package/src/features/pdf-viewer/PdfPageSlider.tsx +5 -5
- package/src/features/pdf-viewer/SimplePdfViewer.tsx +11 -12
- package/src/features/permissions/UserPermissionsProvider.tsx +36 -39
- package/src/features/store/objects/DocumentQuickFilter.tsx +13 -15
- package/src/features/store/objects/DocumentSearchResults.tsx +3 -6
- package/src/features/store/objects/components/ContentDispositionButton.tsx +5 -1
- package/src/features/store/objects/components/ContentOverview.tsx +240 -67
- package/src/features/store/objects/components/DocumentIcon.tsx +1 -1
- package/src/features/store/objects/components/VectorSearchWidget.tsx +2 -2
- package/src/features/store/objects/components/useContentPanelHooks.ts +3 -15
- package/src/features/store/objects/layout/renderers.tsx +1 -2
- package/src/features/store/objects/selection/SelectionActions.tsx +1 -1
- package/src/features/store/types/ContentObjectTypesTable.tsx +12 -14
- package/src/features/store/types/IntakePolicyEditor.tsx +679 -0
- package/src/features/store/types/IntakePolicyForm.tsx +838 -0
- package/src/features/store/types/SelectContentTypeModal.tsx +2 -2
- package/src/features/store/types/index.ts +2 -0
- package/src/features/store/types/intake-policy-editor.logic.test.ts +109 -0
- package/src/features/store/types/intake-policy-editor.logic.ts +87 -0
- package/src/features/store/types/typeCatalogCache.ts +45 -0
- package/src/features/user/UserInfo.tsx +7 -7
- package/src/features/views/ViewExperience.test.tsx +273 -0
- package/src/features/views/ViewExperience.tsx +408 -0
- package/src/features/views/ViewNavigation.test.tsx +86 -0
- package/src/features/views/ViewNavigation.tsx +187 -0
- package/src/features/views/ViewResults.test.ts +82 -0
- package/src/features/views/ViewResults.tsx +609 -0
- package/src/features/views/ViewSearch.test.tsx +147 -0
- package/src/features/views/ViewSearch.tsx +205 -0
- package/src/features/views/index.ts +6 -0
- package/src/features/views/types.ts +57 -0
- package/src/features/views/viewState.test.ts +111 -0
- package/src/features/views/viewState.ts +132 -0
- package/src/i18n/locales/ar.json +18 -0
- package/src/i18n/locales/de.json +14 -0
- package/src/i18n/locales/en.json +965 -751
- package/src/i18n/locales/es.json +15 -0
- package/src/i18n/locales/fr.json +15 -0
- package/src/i18n/locales/it.json +15 -0
- package/src/i18n/locales/ja.json +13 -0
- package/src/i18n/locales/ko.json +13 -0
- package/src/i18n/locales/pt.json +15 -0
- package/src/i18n/locales/ru.json +16 -0
- package/src/i18n/locales/tr.json +14 -0
- package/src/i18n/locales/zh-TW.json +13 -0
- package/src/i18n/locales/zh.json +13 -0
- package/src/router/HistoryNavigator.test.ts +48 -0
- package/src/router/HistoryNavigator.ts +28 -14
- package/src/router/Nav.tsx +24 -8
- package/src/router/Router.tsx +18 -4
- package/src/router/path.test.ts +87 -0
- package/src/router/path.ts +38 -0
- package/src/session/UserSession.test.ts +39 -0
- package/src/session/UserSession.ts +37 -12
- package/src/session/UserSessionProvider.tsx +138 -62
- package/src/session/auth/composable.test.ts +111 -0
- package/src/session/auth/composable.ts +58 -3
- package/src/session/auth/domainRouting.test.ts +28 -1
- package/src/session/auth/domainRouting.ts +38 -0
- package/src/shell/login/SignInRestrictedEnvStep.tsx +1 -1
- package/src/shell/login/SigninScreen.tsx +9 -1
- package/src/shell/login/productionUrl.test.ts +8 -5
- package/src/shell/login/productionUrl.ts +15 -5
- package/src/widgets/form/Form.tsx +1 -1
- package/src/widgets/json-view/JSONSwitcher.tsx +2 -2
- package/src/widgets/json-view/JSONView.tsx +7 -7
- package/src/widgets/monacoEditor/MonacoEditor.tsx +29 -0
- package/src/widgets/schema-editor/ManagedSchema.ts +22 -0
- package/src/widgets/schema-editor/editor/EditableSchemaProperty.tsx +3 -0
- package/src/widgets/schema-editor/editor/PropertyViewer.tsx +14 -3
- package/src/widgets/schema-editor/editor/SchemaEditor.tsx +80 -29
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { getMountBasename, joinPath, stripMountBasename, withMountBasename } from './path';
|
|
3
|
+
|
|
4
|
+
// Simulate a served `<base href>` (or its absence) by stubbing document.querySelector + document.URL.
|
|
5
|
+
function setBaseHref(href: string | null, docUrl = 'https://gw.example.com/x') {
|
|
6
|
+
(globalThis as { document?: unknown }).document = {
|
|
7
|
+
URL: docUrl,
|
|
8
|
+
querySelector: (sel: string) =>
|
|
9
|
+
sel === 'base[href]' && href ? ({ href: new URL(href, docUrl).href } as unknown) : null,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const MOUNT = '/tenants/05948c_5ed5f4/apps/furniture-catalog/versions/20260620T064257113Z/app';
|
|
14
|
+
|
|
15
|
+
describe('mount basename helpers', () => {
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
delete (globalThis as { document?: unknown }).document;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('getMountBasename', () => {
|
|
21
|
+
it('returns the base path (no trailing slash) when a deep <base href> is present', () => {
|
|
22
|
+
setBaseHref(`${MOUNT}/`);
|
|
23
|
+
expect(getMountBasename()).toBe(MOUNT);
|
|
24
|
+
});
|
|
25
|
+
it('returns "" for the Studio UI (no <base> element)', () => {
|
|
26
|
+
setBaseHref(null);
|
|
27
|
+
expect(getMountBasename()).toBe('');
|
|
28
|
+
});
|
|
29
|
+
it('returns "" for a root <base href="/">', () => {
|
|
30
|
+
setBaseHref('/');
|
|
31
|
+
expect(getMountBasename()).toBe('');
|
|
32
|
+
});
|
|
33
|
+
it('returns "" when there is no document (SSR)', () => {
|
|
34
|
+
delete (globalThis as { document?: unknown }).document;
|
|
35
|
+
expect(getMountBasename()).toBe('');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('withMountBasename (navigate side)', () => {
|
|
40
|
+
it('prepends the mount to an absolute app route', () => {
|
|
41
|
+
setBaseHref(`${MOUNT}/`);
|
|
42
|
+
expect(withMountBasename('/assistant')).toBe(`${MOUNT}/assistant`);
|
|
43
|
+
expect(withMountBasename('/')).toBe(`${MOUNT}/`);
|
|
44
|
+
});
|
|
45
|
+
it('does not double-prepend a path already under the mount', () => {
|
|
46
|
+
setBaseHref(`${MOUNT}/`);
|
|
47
|
+
expect(withMountBasename(`${MOUNT}/items/5`)).toBe(`${MOUNT}/items/5`);
|
|
48
|
+
expect(withMountBasename(MOUNT)).toBe(MOUNT);
|
|
49
|
+
});
|
|
50
|
+
it('leaves relative paths untouched', () => {
|
|
51
|
+
setBaseHref(`${MOUNT}/`);
|
|
52
|
+
expect(withMountBasename('assistant')).toBe('assistant');
|
|
53
|
+
});
|
|
54
|
+
it('is a no-op for the Studio UI (no mount)', () => {
|
|
55
|
+
setBaseHref(null);
|
|
56
|
+
expect(withMountBasename('/assistant')).toBe('/assistant');
|
|
57
|
+
expect(withMountBasename('/')).toBe('/');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('stripMountBasename (match side)', () => {
|
|
62
|
+
it('strips the mount so route patterns match app-relative', () => {
|
|
63
|
+
setBaseHref(`${MOUNT}/`);
|
|
64
|
+
expect(stripMountBasename(`${MOUNT}/assistant`)).toBe('/assistant');
|
|
65
|
+
expect(stripMountBasename(MOUNT)).toBe('/');
|
|
66
|
+
expect(stripMountBasename(`${MOUNT}/`)).toBe('/');
|
|
67
|
+
});
|
|
68
|
+
it('leaves a path not under the mount untouched', () => {
|
|
69
|
+
setBaseHref(`${MOUNT}/`);
|
|
70
|
+
expect(stripMountBasename('/somewhere-else')).toBe('/somewhere-else');
|
|
71
|
+
});
|
|
72
|
+
it('is a no-op for the Studio UI (no mount)', () => {
|
|
73
|
+
setBaseHref(null);
|
|
74
|
+
expect(stripMountBasename('/agents/123')).toBe('/agents/123');
|
|
75
|
+
expect(stripMountBasename('/')).toBe('/');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('round-trips: strip(with(route)) === route under a mount', () => {
|
|
80
|
+
setBaseHref(`${MOUNT}/`);
|
|
81
|
+
for (const route of ['/', '/assistant', '/items/5']) {
|
|
82
|
+
expect(stripMountBasename(withMountBasename(route))).toBe(route === '/' ? '/' : route);
|
|
83
|
+
}
|
|
84
|
+
// sanity: joinPath stays available/correct
|
|
85
|
+
expect(joinPath(MOUNT, '/assistant')).toBe(`${MOUNT}/assistant`);
|
|
86
|
+
});
|
|
87
|
+
});
|
package/src/router/path.ts
CHANGED
|
@@ -19,6 +19,44 @@ export function joinPath(path1: string, path2: string) {
|
|
|
19
19
|
return path2;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* The mount base path from an explicit `<base href>` element, or `''` when there is none.
|
|
24
|
+
*
|
|
25
|
+
* Apps served under a deep gateway mount carry `<base href="/tenants/.../app/">`, injected at serve
|
|
26
|
+
* time. The router otherwise resolves absolute routes against the origin, which collapses the URL
|
|
27
|
+
* off the mount (and breaks in-app links). This returns the base pathname without a trailing slash
|
|
28
|
+
* so {@link withMountBasename} can keep navigation under the mount and {@link stripMountBasename}
|
|
29
|
+
* can match routes app-relative. The Studio UI has no `<base>` element, so this returns `''` and
|
|
30
|
+
* every caller becomes a no-op there.
|
|
31
|
+
*/
|
|
32
|
+
export function getMountBasename(): string {
|
|
33
|
+
if (typeof document === 'undefined') return '';
|
|
34
|
+
const base = document.querySelector('base[href]') as HTMLBaseElement | null;
|
|
35
|
+
if (!base) return '';
|
|
36
|
+
try {
|
|
37
|
+
return new URL(base.href, document.URL).pathname.replace(/\/+$/, '');
|
|
38
|
+
} catch {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Prepend the mount basename to an absolute app path (no-op when no mount, path is relative, or already prefixed). */
|
|
44
|
+
export function withMountBasename(path: string): string {
|
|
45
|
+
const base = getMountBasename();
|
|
46
|
+
if (!base || !path.startsWith('/')) return path;
|
|
47
|
+
if (path === base || path.startsWith(`${base}/`)) return path;
|
|
48
|
+
return joinPath(base, path);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Strip the mount basename from a browser path so route patterns match app-relative (no-op when no mount). */
|
|
52
|
+
export function stripMountBasename(path: string): string {
|
|
53
|
+
const base = getMountBasename();
|
|
54
|
+
if (!base) return path;
|
|
55
|
+
if (path === base) return '/';
|
|
56
|
+
if (path.startsWith(`${base}/`)) return path.slice(base.length) || '/';
|
|
57
|
+
return path;
|
|
58
|
+
}
|
|
59
|
+
|
|
22
60
|
export function getPathSegments(path: string) {
|
|
23
61
|
if (path === '') {
|
|
24
62
|
return [];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { VertesiaClient } from '@vertesia/client';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { UserSession } from './UserSession';
|
|
4
|
+
|
|
5
|
+
function createSession(onboardingProgress: () => Promise<Record<string, boolean>>) {
|
|
6
|
+
const client = {
|
|
7
|
+
account: { onboardingProgress },
|
|
8
|
+
} as unknown as VertesiaClient;
|
|
9
|
+
const session = new UserSession(client);
|
|
10
|
+
session.setSession = vi.fn();
|
|
11
|
+
return session;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('UserSession.fetchOnboardingStatus', () => {
|
|
15
|
+
it('does not replace the session when an incomplete status is unchanged', async () => {
|
|
16
|
+
const session = createSession(async () => ({ project_created: false }));
|
|
17
|
+
session.onboardingComplete = false;
|
|
18
|
+
|
|
19
|
+
await expect(session.fetchOnboardingStatus()).resolves.toBe(false);
|
|
20
|
+
expect(session.setSession).not.toHaveBeenCalled();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('updates the session and reports when onboarding becomes complete', async () => {
|
|
24
|
+
const session = createSession(async () => ({ project_created: true }));
|
|
25
|
+
session.onboardingComplete = false;
|
|
26
|
+
|
|
27
|
+
await expect(session.fetchOnboardingStatus()).resolves.toBe(true);
|
|
28
|
+
expect(session.setSession).toHaveBeenCalledOnce();
|
|
29
|
+
expect(vi.mocked(session.setSession!).mock.calls[0]?.[0]?.onboardingComplete).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('publishes the initial incomplete status without reporting completion', async () => {
|
|
33
|
+
const session = createSession(async () => ({ project_created: false }));
|
|
34
|
+
|
|
35
|
+
await expect(session.fetchOnboardingStatus()).resolves.toBe(false);
|
|
36
|
+
expect(session.setSession).toHaveBeenCalledOnce();
|
|
37
|
+
expect(vi.mocked(session.setSession!).mock.calls[0]?.[0]?.onboardingComplete).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -5,7 +5,7 @@ import { jwtDecode } from 'jwt-decode';
|
|
|
5
5
|
import { createContext, useContext } from 'react';
|
|
6
6
|
|
|
7
7
|
import { getComposableToken } from './auth/composable';
|
|
8
|
-
import { shouldRedirectToCentralAuth } from './auth/domainRouting';
|
|
8
|
+
import { authReturnUrl, mountRootUrl, shouldRedirectToCentralAuth } from './auth/domainRouting';
|
|
9
9
|
import { getFirebaseAuth } from './auth/firebase';
|
|
10
10
|
|
|
11
11
|
import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY } from './constants';
|
|
@@ -84,6 +84,22 @@ class UserSession {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Force a fresh Vertesia JWT from STS, bypassing the in-memory cache.
|
|
89
|
+
* Use this when the current token's claims (e.g. `apps`) are suspected
|
|
90
|
+
* stale — STS recomputes the `apps` claim from ACEs on every issuance.
|
|
91
|
+
*/
|
|
92
|
+
async refreshAuthToken(): Promise<AuthTokenPayload> {
|
|
93
|
+
const res = await getComposableToken(undefined, undefined, undefined, true);
|
|
94
|
+
const token = res?.rawToken;
|
|
95
|
+
if (!token) {
|
|
96
|
+
throw new Error('No token available');
|
|
97
|
+
}
|
|
98
|
+
this.authToken = jwtDecode(token) as unknown as AuthTokenPayload;
|
|
99
|
+
this.setSession?.(this.clone());
|
|
100
|
+
return this.authToken;
|
|
101
|
+
}
|
|
102
|
+
|
|
87
103
|
signOut() {
|
|
88
104
|
//compatibility
|
|
89
105
|
this.logout();
|
|
@@ -136,8 +152,7 @@ class UserSession {
|
|
|
136
152
|
this.client.withAuthCallback(undefined);
|
|
137
153
|
|
|
138
154
|
const logoutUrl = new URL(CENTRAL_AUTH_REDIRECT);
|
|
139
|
-
const currentUrl =
|
|
140
|
-
currentUrl.hash = '';
|
|
155
|
+
const currentUrl = authReturnUrl();
|
|
141
156
|
logoutUrl.pathname = '/logout';
|
|
142
157
|
logoutUrl.searchParams.set('redirect_uri', currentUrl.toString());
|
|
143
158
|
location.replace(logoutUrl.toString());
|
|
@@ -153,12 +168,13 @@ class UserSession {
|
|
|
153
168
|
this.authToken = undefined;
|
|
154
169
|
this.setSession = undefined;
|
|
155
170
|
this.client.withAuthCallback(undefined);
|
|
156
|
-
// Navigate to root to avoid React rendering errors when
|
|
171
|
+
// Navigate to the app root to avoid React rendering errors when
|
|
157
172
|
// unmounting deeply nested route components during logout.
|
|
158
|
-
//
|
|
159
|
-
//
|
|
173
|
+
// Use the mount root (not bare '/') so a gateway-mounted app stays
|
|
174
|
+
// on its mount. Only navigate if user was actually logged in to
|
|
175
|
+
// avoid an infinite reload loop on fresh/incognito sessions.
|
|
160
176
|
if (wasLoggedIn) {
|
|
161
|
-
location.replace(
|
|
177
|
+
location.replace(mountRootUrl().toString());
|
|
162
178
|
}
|
|
163
179
|
}
|
|
164
180
|
}
|
|
@@ -173,7 +189,9 @@ class UserSession {
|
|
|
173
189
|
}
|
|
174
190
|
}
|
|
175
191
|
|
|
176
|
-
|
|
192
|
+
const url = mountRootUrl();
|
|
193
|
+
url.searchParams.set('a', targetAccountId);
|
|
194
|
+
window.location.replace(url.toString());
|
|
177
195
|
}
|
|
178
196
|
|
|
179
197
|
async switchProject(targetProjectId: string) {
|
|
@@ -181,7 +199,12 @@ class UserSession {
|
|
|
181
199
|
localStorage.setItem(`${LastSelectedProjectId_KEY}-${this.account.id}`, targetProjectId);
|
|
182
200
|
}
|
|
183
201
|
|
|
184
|
-
|
|
202
|
+
const url = mountRootUrl();
|
|
203
|
+
if (this.account?.id) {
|
|
204
|
+
url.searchParams.set('a', this.account.id);
|
|
205
|
+
}
|
|
206
|
+
url.searchParams.set('p', targetProjectId);
|
|
207
|
+
window.location.replace(url.toString());
|
|
185
208
|
}
|
|
186
209
|
|
|
187
210
|
async fetchAccounts() {
|
|
@@ -237,13 +260,15 @@ class UserSession {
|
|
|
237
260
|
const onboarding = await this.client.account.onboardingProgress();
|
|
238
261
|
this.onboardingComplete = Object.values(onboarding).every((value) => value === true);
|
|
239
262
|
if (previousStatus !== this.onboardingComplete) {
|
|
240
|
-
|
|
263
|
+
this.setSession?.(this.clone());
|
|
241
264
|
}
|
|
242
|
-
this.
|
|
265
|
+
return previousStatus === false && this.onboardingComplete;
|
|
243
266
|
} catch (error) {
|
|
244
267
|
console.error('Error fetching onboarding status:', error);
|
|
245
268
|
this.onboardingComplete = false;
|
|
246
|
-
|
|
269
|
+
if (previousStatus !== this.onboardingComplete) {
|
|
270
|
+
this.setSession?.(this.clone());
|
|
271
|
+
}
|
|
247
272
|
}
|
|
248
273
|
return false;
|
|
249
274
|
}
|
|
@@ -2,13 +2,19 @@ import { Env } from '@vertesia/ui/env';
|
|
|
2
2
|
import { onAuthStateChanged } from 'firebase/auth';
|
|
3
3
|
import { type ReactNode, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { getComposableToken, RestrictedEnvironmentError, STSError, UserNotFoundError } from './auth/composable';
|
|
5
|
-
import { shouldRedirectToCentralAuth } from './auth/domainRouting';
|
|
5
|
+
import { authReturnUrl, shouldRedirectToCentralAuth } from './auth/domainRouting';
|
|
6
6
|
import { getFirebaseAuth } from './auth/firebase';
|
|
7
7
|
import { useAuthState } from './auth/useAuthState';
|
|
8
8
|
import { LastSelectedAccountId_KEY, LastSelectedProjectId_KEY, UserSession, UserSessionContext } from './UserSession';
|
|
9
9
|
|
|
10
10
|
const CENTRAL_AUTH_REDIRECT = 'https://internal-auth.vertesia.app/';
|
|
11
11
|
|
|
12
|
+
function clearAuthHash() {
|
|
13
|
+
const url = new URL(window.location.href);
|
|
14
|
+
url.hash = '';
|
|
15
|
+
window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
interface UserSessionProviderProps {
|
|
13
19
|
children: ReactNode | ReactNode[];
|
|
14
20
|
loadOnboardingStatus?: boolean;
|
|
@@ -24,8 +30,7 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
|
|
|
24
30
|
|
|
25
31
|
const redirectToCentralAuth = (projectId?: string, accountId?: string) => {
|
|
26
32
|
const url = new URL(`${CENTRAL_AUTH_REDIRECT}?sts=${Env.endpoints.sts ?? 'https://sts.vertesia.io'}`);
|
|
27
|
-
const currentUrl =
|
|
28
|
-
currentUrl.hash = '';
|
|
33
|
+
const currentUrl = authReturnUrl();
|
|
29
34
|
if (projectId) currentUrl.searchParams.set('p', projectId);
|
|
30
35
|
if (accountId) currentUrl.searchParams.set('a', accountId);
|
|
31
36
|
url.searchParams.set('redirect_uri', currentUrl.toString());
|
|
@@ -59,6 +64,28 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
|
|
|
59
64
|
},
|
|
60
65
|
});
|
|
61
66
|
|
|
67
|
+
if (Env.isLocalDev && Env.devAuthToken) {
|
|
68
|
+
session.setSession = setSession;
|
|
69
|
+
getComposableToken(selectedAccount, selectedProject, Env.devAuthToken)
|
|
70
|
+
.then((res) => {
|
|
71
|
+
session.login(res.rawToken).then(() => setSession(session.clone()));
|
|
72
|
+
})
|
|
73
|
+
.catch((err) => {
|
|
74
|
+
console.error('Failed to initialize dev auth token', err);
|
|
75
|
+
Env.logger.error('Failed to initialize dev auth token', {
|
|
76
|
+
vertesia: {
|
|
77
|
+
account_id: selectedAccount,
|
|
78
|
+
project_id: selectedProject,
|
|
79
|
+
error: err,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
session.isLoading = false;
|
|
83
|
+
session.authError = err instanceof Error ? err : new Error(String(err));
|
|
84
|
+
setSession(session.clone());
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
62
89
|
if (token && state) {
|
|
63
90
|
session.setSession = setSession;
|
|
64
91
|
const validationError = verifyState(state);
|
|
@@ -77,8 +104,7 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
|
|
|
77
104
|
.then((res) => {
|
|
78
105
|
session.login(res.rawToken, { loadOnboardingStatus }).then(() => {
|
|
79
106
|
setSession(session.clone());
|
|
80
|
-
|
|
81
|
-
window.location.hash = '';
|
|
107
|
+
clearAuthHash();
|
|
82
108
|
});
|
|
83
109
|
})
|
|
84
110
|
.catch((err) => {
|
|
@@ -117,7 +143,7 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
|
|
|
117
143
|
);
|
|
118
144
|
session.logout();
|
|
119
145
|
setSession(session.clone());
|
|
120
|
-
|
|
146
|
+
clearAuthHash();
|
|
121
147
|
return;
|
|
122
148
|
}
|
|
123
149
|
|
|
@@ -130,7 +156,14 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
|
|
|
130
156
|
redirectToCentralAuth();
|
|
131
157
|
});
|
|
132
158
|
return;
|
|
133
|
-
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let cancelled = false;
|
|
162
|
+
let unsubscribe: (() => void) | undefined;
|
|
163
|
+
|
|
164
|
+
const startFirebaseOrCentralAuth = () => {
|
|
165
|
+
if (cancelled) return;
|
|
166
|
+
|
|
134
167
|
// If the current host is not in the Firebase allowlist, central auth owns sign-in.
|
|
135
168
|
if (!session.isLoggedIn()) {
|
|
136
169
|
console.log('Auth: not logged in & no token/state');
|
|
@@ -154,71 +187,114 @@ export function UserSessionProvider({ children, loadOnboardingStatus = true }: U
|
|
|
154
187
|
});
|
|
155
188
|
redirectToCentralAuth();
|
|
156
189
|
return; // Don't register onAuthStateChanged listener when redirecting
|
|
157
|
-
} else {
|
|
158
|
-
console.log('Auth: host is in Firebase auth allowlist');
|
|
159
|
-
Env.logger.info('Host is in Firebase auth allowlist', {
|
|
160
|
-
vertesia: {
|
|
161
|
-
account_id: selectedAccount,
|
|
162
|
-
project_id: selectedProject,
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
190
|
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
191
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
console.log('Auth: successful login with firebase');
|
|
172
|
-
Env.logger.info('Successful login with firebase', {
|
|
192
|
+
console.log('Auth: host is in Firebase auth allowlist');
|
|
193
|
+
Env.logger.info('Host is in Firebase auth allowlist', {
|
|
173
194
|
vertesia: {
|
|
174
195
|
account_id: selectedAccount,
|
|
175
196
|
project_id: selectedProject,
|
|
176
197
|
},
|
|
177
198
|
});
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
})
|
|
189
|
-
.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
unsubscribe = onAuthStateChanged(getFirebaseAuth(), async (firebaseUser) => {
|
|
202
|
+
if (firebaseUser) {
|
|
203
|
+
console.log('Auth: successful login with firebase');
|
|
204
|
+
Env.logger.info('Successful login with firebase', {
|
|
205
|
+
vertesia: {
|
|
206
|
+
account_id: selectedAccount,
|
|
207
|
+
project_id: selectedProject,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
session.setSession = setSession;
|
|
211
|
+
await getComposableToken(
|
|
212
|
+
selectedAccount,
|
|
213
|
+
selectedProject,
|
|
214
|
+
undefined,
|
|
215
|
+
false,
|
|
216
|
+
shouldRedirectToCentralAuth(),
|
|
217
|
+
)
|
|
218
|
+
.then((res) => {
|
|
219
|
+
session
|
|
220
|
+
.login(res.rawToken, { loadOnboardingStatus })
|
|
221
|
+
.then(() => setSession(session.clone()));
|
|
222
|
+
})
|
|
223
|
+
.catch((err) => {
|
|
224
|
+
console.error('Failed to fetch user token from studio', err);
|
|
225
|
+
Env.logger.error('Failed to fetch user token from studio', {
|
|
226
|
+
vertesia: {
|
|
227
|
+
account_id: selectedAccount,
|
|
228
|
+
project_id: selectedProject,
|
|
229
|
+
error: err,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
// Keep the Firebase session for UserNotFoundError (signup flow) and
|
|
233
|
+
// RestrictedEnvironmentError (identity is valid; the environment is gated).
|
|
234
|
+
// Logging out would retrigger onAuthStateChanged and clear the authError.
|
|
235
|
+
if (!(err instanceof UserNotFoundError) && !(err instanceof RestrictedEnvironmentError)) {
|
|
236
|
+
session.logout();
|
|
237
|
+
}
|
|
238
|
+
session.isLoading = false;
|
|
239
|
+
session.authError = err;
|
|
240
|
+
setSession(session.clone());
|
|
197
241
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
setSession(session.clone());
|
|
242
|
+
} else {
|
|
243
|
+
// anonymous user
|
|
244
|
+
console.log('Auth: using anonymous user');
|
|
245
|
+
Env.logger.info('Using anonymous user', {
|
|
246
|
+
vertesia: {
|
|
247
|
+
account_id: selectedAccount,
|
|
248
|
+
project_id: selectedProject,
|
|
249
|
+
},
|
|
207
250
|
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
251
|
+
session.client.withAuthCallback(undefined);
|
|
252
|
+
session.logout();
|
|
253
|
+
setSession(session.clone());
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
if (Env.authTokenProvider) {
|
|
259
|
+
session.setSession = setSession;
|
|
260
|
+
void Env.authTokenProvider()
|
|
261
|
+
.then(async (injectedToken) => {
|
|
262
|
+
if (!injectedToken) {
|
|
263
|
+
startFirebaseOrCentralAuth();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const res = await getComposableToken(
|
|
267
|
+
selectedAccount,
|
|
268
|
+
selectedProject,
|
|
269
|
+
injectedToken,
|
|
270
|
+
false,
|
|
271
|
+
shouldRedirectToCentralAuth(),
|
|
272
|
+
);
|
|
273
|
+
await session.login(res.rawToken, { loadOnboardingStatus });
|
|
274
|
+
if (!cancelled) setSession(session.clone());
|
|
275
|
+
})
|
|
276
|
+
.catch((err: unknown) => {
|
|
277
|
+
console.warn('Auth: failed to initialize injected auth token', err);
|
|
278
|
+
Env.logger.warn('Failed to initialize injected auth token', {
|
|
279
|
+
vertesia: {
|
|
280
|
+
account_id: selectedAccount,
|
|
281
|
+
project_id: selectedProject,
|
|
282
|
+
error: err,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
startFirebaseOrCentralAuth();
|
|
216
286
|
});
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
}
|
|
287
|
+
return () => {
|
|
288
|
+
cancelled = true;
|
|
289
|
+
unsubscribe?.();
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
startFirebaseOrCentralAuth();
|
|
294
|
+
return () => {
|
|
295
|
+
cancelled = true;
|
|
296
|
+
unsubscribe?.();
|
|
297
|
+
};
|
|
222
298
|
};
|
|
223
299
|
|
|
224
300
|
useEffect(() => {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
function makeJwt(payload: Record<string, unknown>) {
|
|
4
|
+
const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
5
|
+
return `${encode({ alg: 'ES256', typ: 'JWT' })}.${encode(payload)}.signature`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function importComposableAuth() {
|
|
9
|
+
vi.resetModules();
|
|
10
|
+
const [{ Env }, composableAuth] = await Promise.all([import('@vertesia/ui/env'), import('./composable')]);
|
|
11
|
+
Env.init({
|
|
12
|
+
name: 'test',
|
|
13
|
+
version: '0.0.0',
|
|
14
|
+
isLocalDev: false,
|
|
15
|
+
isDocker: false,
|
|
16
|
+
type: 'test',
|
|
17
|
+
endpoints: {
|
|
18
|
+
studio: 'https://studio-server-dev-feat-appgen.api.dev1.vertesia.io',
|
|
19
|
+
zeno: 'https://zeno-server-dev-feat-appgen.api.dev1.vertesia.io',
|
|
20
|
+
sts: 'https://sts.dev1.vertesia.io',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
return composableAuth;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('getComposableToken', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
localStorage.clear();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
vi.unstubAllGlobals();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('uses an authorization-bearing STS-issued Vertesia token directly instead of exchanging it', async () => {
|
|
36
|
+
const token = makeJwt({
|
|
37
|
+
iss: 'https://sts.dev1.vertesia.io',
|
|
38
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
39
|
+
account: { id: 'account-id', name: 'Account' },
|
|
40
|
+
account_roles: [],
|
|
41
|
+
project: { id: 'project-id', name: 'Project', account: 'account-id' },
|
|
42
|
+
project_roles: ['developer'],
|
|
43
|
+
});
|
|
44
|
+
const fetchMock = vi.fn();
|
|
45
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
46
|
+
|
|
47
|
+
const { getComposableToken } = await importComposableAuth();
|
|
48
|
+
const result = await getComposableToken('account-id', 'project-id', token, false, true);
|
|
49
|
+
|
|
50
|
+
expect(result.rawToken).toBe(token);
|
|
51
|
+
expect(result.token.iss).toBe('https://sts.dev1.vertesia.io');
|
|
52
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('exchanges a bare STS-issued Vertesia token so roles can be hydrated', async () => {
|
|
56
|
+
const sourceToken = makeJwt({
|
|
57
|
+
iss: 'https://sts.dev1.vertesia.io',
|
|
58
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
59
|
+
account: { id: 'account-id', name: 'Account' },
|
|
60
|
+
project: { id: 'project-id', name: 'Project', account: 'account-id' },
|
|
61
|
+
});
|
|
62
|
+
const exchangedToken = makeJwt({
|
|
63
|
+
iss: 'https://sts.dev1.vertesia.io',
|
|
64
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
65
|
+
account: { id: 'account-id', name: 'Account' },
|
|
66
|
+
account_roles: [],
|
|
67
|
+
project: { id: 'project-id', name: 'Project', account: 'account-id' },
|
|
68
|
+
project_roles: ['admin'],
|
|
69
|
+
});
|
|
70
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
71
|
+
ok: true,
|
|
72
|
+
status: 200,
|
|
73
|
+
json: () => Promise.resolve({ token: exchangedToken }),
|
|
74
|
+
});
|
|
75
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
76
|
+
|
|
77
|
+
const { getComposableToken } = await importComposableAuth();
|
|
78
|
+
const result = await getComposableToken('account-id', 'project-id', sourceToken, false, true);
|
|
79
|
+
|
|
80
|
+
expect(result.rawToken).toBe(exchangedToken);
|
|
81
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
82
|
+
expect(fetchMock.mock.calls[0]?.[0]?.toString()).toBe('https://sts.dev1.vertesia.io/token/issue');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('still exchanges a non-STS source token', async () => {
|
|
86
|
+
const sourceToken = makeJwt({
|
|
87
|
+
iss: 'https://securetoken.google.com/example',
|
|
88
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
89
|
+
email: 'user@example.com',
|
|
90
|
+
});
|
|
91
|
+
const exchangedToken = makeJwt({
|
|
92
|
+
iss: 'https://sts.dev1.vertesia.io',
|
|
93
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
94
|
+
account: { id: 'account-id', name: 'Account' },
|
|
95
|
+
project: { id: 'project-id', name: 'Project', account: 'account-id' },
|
|
96
|
+
});
|
|
97
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
98
|
+
ok: true,
|
|
99
|
+
status: 200,
|
|
100
|
+
json: () => Promise.resolve({ token: exchangedToken }),
|
|
101
|
+
});
|
|
102
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
103
|
+
|
|
104
|
+
const { getComposableToken } = await importComposableAuth();
|
|
105
|
+
const result = await getComposableToken('account-id', 'project-id', sourceToken, false, true);
|
|
106
|
+
|
|
107
|
+
expect(result.rawToken).toBe(exchangedToken);
|
|
108
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
109
|
+
expect(fetchMock.mock.calls[0]?.[0]?.toString()).toBe('https://sts.dev1.vertesia.io/token/issue');
|
|
110
|
+
});
|
|
111
|
+
});
|