@robosystems/core 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/actions/entity-actions.d.ts +8 -0
- package/actions/entity-actions.js +16 -0
- package/actions/graph-actions.d.ts +3 -0
- package/actions/graph-actions.js +13 -0
- package/auth-components/AppSwitcher.d.ts +6 -0
- package/auth-components/AppSwitcher.js +28 -0
- package/auth-components/AuthGuard.d.ts +7 -0
- package/auth-components/AuthGuard.js +31 -0
- package/auth-components/AuthProvider.d.ts +9 -0
- package/auth-components/AuthProvider.js +565 -0
- package/auth-components/SessionWarningDialog.d.ts +1 -0
- package/auth-components/SessionWarningDialog.js +51 -0
- package/auth-components/SignInForm.d.ts +20 -0
- package/auth-components/SignInForm.js +139 -0
- package/auth-components/SignUpForm.d.ts +15 -0
- package/auth-components/SignUpForm.js +167 -0
- package/auth-components/TurnstileWidget.d.ts +99 -0
- package/auth-components/TurnstileWidget.js +224 -0
- package/auth-components/index.d.ts +6 -0
- package/auth-components/index.js +6 -0
- package/auth-core/cleanup.d.ts +44 -0
- package/auth-core/cleanup.js +157 -0
- package/auth-core/client.d.ts +121 -0
- package/auth-core/client.js +767 -0
- package/auth-core/config.d.ts +19 -0
- package/auth-core/config.js +56 -0
- package/auth-core/hooks.d.ts +19 -0
- package/auth-core/hooks.js +63 -0
- package/auth-core/index.d.ts +6 -0
- package/auth-core/index.js +6 -0
- package/auth-core/sso.d.ts +42 -0
- package/auth-core/sso.js +281 -0
- package/auth-core/token-storage.d.ts +58 -0
- package/auth-core/token-storage.js +177 -0
- package/auth-core/types.d.ts +145 -0
- package/auth-core/types.js +1 -0
- package/components/EntitySelector.d.ts +44 -0
- package/components/EntitySelector.js +64 -0
- package/components/EntitySelectorCore.d.ts +45 -0
- package/components/EntitySelectorCore.js +67 -0
- package/components/GraphSelectorCore.d.ts +21 -0
- package/components/GraphSelectorCore.js +39 -0
- package/components/PageLayout.d.ts +8 -0
- package/components/PageLayout.js +5 -0
- package/components/RepositoryGuard.d.ts +68 -0
- package/components/RepositoryGuard.js +91 -0
- package/components/console/ConsoleContent.d.ts +4 -0
- package/components/console/ConsoleContent.js +612 -0
- package/components/console/ConsoleMarkdown.d.ts +11 -0
- package/components/console/ConsoleMarkdown.js +49 -0
- package/components/console/ProgressiveText.d.ts +6 -0
- package/components/console/ProgressiveText.js +21 -0
- package/components/console/graphAwareConfig.d.ts +70 -0
- package/components/console/graphAwareConfig.js +411 -0
- package/components/console/index.d.ts +6 -0
- package/components/console/index.js +4 -0
- package/components/console/types.d.ts +84 -0
- package/components/console/types.js +1 -0
- package/components/graph-filters.d.ts +75 -0
- package/components/graph-filters.js +111 -0
- package/components/index.d.ts +10 -0
- package/components/index.js +9 -0
- package/components/repositories/ActiveSubscriptions.d.ts +26 -0
- package/components/repositories/ActiveSubscriptions.js +103 -0
- package/components/repositories/BrowseRepositories.d.ts +11 -0
- package/components/repositories/BrowseRepositories.js +184 -0
- package/components/repositories/index.d.ts +2 -0
- package/components/repositories/index.js +2 -0
- package/components/search/SearchContent.d.ts +4 -0
- package/components/search/SearchContent.js +168 -0
- package/components/search/index.d.ts +2 -0
- package/components/search/index.js +1 -0
- package/components/search/types.d.ts +15 -0
- package/components/search/types.js +1 -0
- package/contexts/entity-context.d.ts +28 -0
- package/contexts/entity-context.js +89 -0
- package/contexts/graph-context.d.ts +27 -0
- package/contexts/graph-context.js +128 -0
- package/contexts/index.d.ts +5 -0
- package/contexts/index.js +5 -0
- package/contexts/org-context.d.ts +25 -0
- package/contexts/org-context.js +64 -0
- package/contexts/service-offerings-context.d.ts +45 -0
- package/contexts/service-offerings-context.js +98 -0
- package/contexts/sidebar-context.d.ts +18 -0
- package/contexts/sidebar-context.js +44 -0
- package/hooks/index.d.ts +6 -0
- package/hooks/index.js +6 -0
- package/hooks/use-api-error.d.ts +3 -0
- package/hooks/use-api-error.js +24 -0
- package/hooks/use-media-query.d.ts +1 -0
- package/hooks/use-media-query.js +21 -0
- package/hooks/use-streaming-query.d.ts +22 -0
- package/hooks/use-streaming-query.js +142 -0
- package/hooks/use-toast.d.ts +37 -0
- package/hooks/use-toast.js +117 -0
- package/hooks/use-user-limits.d.ts +12 -0
- package/hooks/use-user-limits.js +73 -0
- package/hooks/use-user.d.ts +11 -0
- package/hooks/use-user.js +39 -0
- package/index.d.ts +29 -0
- package/index.js +77 -0
- package/lib/entity-cookie.d.ts +10 -0
- package/lib/entity-cookie.js +38 -0
- package/lib/graph-cookie.d.ts +11 -0
- package/lib/graph-cookie.js +62 -0
- package/lib/graph-tiers.d.ts +41 -0
- package/lib/graph-tiers.js +107 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/sidebar-cookie.d.ts +11 -0
- package/lib/sidebar-cookie.js +42 -0
- package/library/colors.d.ts +4 -0
- package/library/colors.js +41 -0
- package/library/components/ClassificationPicker.d.ts +8 -0
- package/library/components/ClassificationPicker.js +55 -0
- package/library/components/ElementBrowser.d.ts +11 -0
- package/library/components/ElementBrowser.js +106 -0
- package/library/components/ElementDetail.d.ts +7 -0
- package/library/components/ElementDetail.js +97 -0
- package/library/components/LibraryHierarchy.d.ts +13 -0
- package/library/components/LibraryHierarchy.js +380 -0
- package/library/components/TaxonomySidebar.d.ts +6 -0
- package/library/components/TaxonomySidebar.js +45 -0
- package/library/index.d.ts +8 -0
- package/library/index.js +7 -0
- package/package.json +37 -0
- package/research/CoverageCard.d.ts +12 -0
- package/research/CoverageCard.js +15 -0
- package/research/CoverageGrid.d.ts +6 -0
- package/research/CoverageGrid.js +9 -0
- package/research/CoverageHistory.d.ts +5 -0
- package/research/CoverageHistory.js +7 -0
- package/research/ResearchArticle.d.ts +15 -0
- package/research/ResearchArticle.js +26 -0
- package/research/catalog.d.ts +14 -0
- package/research/catalog.js +42 -0
- package/research/index.d.ts +6 -0
- package/research/index.js +9 -0
- package/research/types.d.ts +38 -0
- package/research/types.js +4 -0
- package/task-monitoring/hooks.d.ts +26 -0
- package/task-monitoring/hooks.js +126 -0
- package/task-monitoring/index.d.ts +5 -0
- package/task-monitoring/index.js +7 -0
- package/task-monitoring/operationErrors.d.ts +82 -0
- package/task-monitoring/operationErrors.js +251 -0
- package/task-monitoring/operationHooks.d.ts +117 -0
- package/task-monitoring/operationHooks.js +536 -0
- package/task-monitoring/operationMonitor.d.ts +78 -0
- package/task-monitoring/operationMonitor.js +200 -0
- package/task-monitoring/operationTypes.d.ts +236 -0
- package/task-monitoring/operationTypes.js +60 -0
- package/task-monitoring/taskMonitor.d.ts +14 -0
- package/task-monitoring/taskMonitor.js +132 -0
- package/task-monitoring/types.d.ts +31 -0
- package/task-monitoring/types.js +1 -0
- package/theme/flowbite-theme.d.ts +758 -0
- package/theme/flowbite-theme.js +765 -0
- package/theme/index.d.ts +1 -0
- package/theme/index.js +1 -0
- package/types/entity.d.ts +19 -0
- package/types/index.d.ts +2 -0
- package/types/index.js +1 -0
- package/types/user.d.ts +6 -0
- package/ui-components/ConfirmModal.d.ts +41 -0
- package/ui-components/ConfirmModal.js +19 -0
- package/ui-components/EmptyState.d.ts +22 -0
- package/ui-components/EmptyState.js +10 -0
- package/ui-components/LoadingState.d.ts +27 -0
- package/ui-components/LoadingState.js +12 -0
- package/ui-components/Logo.d.ts +28 -0
- package/ui-components/Logo.js +95 -0
- package/ui-components/Spinner.d.ts +37 -0
- package/ui-components/Spinner.js +48 -0
- package/ui-components/StatCard.d.ts +21 -0
- package/ui-components/StatCard.js +10 -0
- package/ui-components/api-keys/ApiKeyDisplay.d.ts +13 -0
- package/ui-components/api-keys/ApiKeyDisplay.js +7 -0
- package/ui-components/api-keys/ApiKeyTable.d.ts +9 -0
- package/ui-components/api-keys/ApiKeyTable.js +17 -0
- package/ui-components/api-keys/ApiKeysCard.d.ts +6 -0
- package/ui-components/api-keys/ApiKeysCard.js +97 -0
- package/ui-components/api-keys/CreateApiKeyModal.d.ts +11 -0
- package/ui-components/api-keys/CreateApiKeyModal.js +47 -0
- package/ui-components/api-keys/SecureApiKeyField.d.ts +13 -0
- package/ui-components/api-keys/SecureApiKeyField.js +64 -0
- package/ui-components/api-keys/index.d.ts +5 -0
- package/ui-components/api-keys/index.js +5 -0
- package/ui-components/chat/ChatHeader.d.ts +12 -0
- package/ui-components/chat/ChatHeader.js +5 -0
- package/ui-components/chat/ChatInputArea.d.ts +16 -0
- package/ui-components/chat/ChatInputArea.js +9 -0
- package/ui-components/chat/ChatMessage.d.ts +7 -0
- package/ui-components/chat/ChatMessage.js +24 -0
- package/ui-components/chat/DeepResearchToggle.d.ts +8 -0
- package/ui-components/chat/DeepResearchToggle.js +13 -0
- package/ui-components/chat/index.d.ts +9 -0
- package/ui-components/chat/index.js +8 -0
- package/ui-components/chat/types.d.ts +23 -0
- package/ui-components/chat/types.js +51 -0
- package/ui-components/forms/PasswordRequirements.d.ts +10 -0
- package/ui-components/forms/PasswordRequirements.js +15 -0
- package/ui-components/forms/SettingsCard.d.ts +11 -0
- package/ui-components/forms/SettingsCard.js +5 -0
- package/ui-components/forms/SettingsFormField.d.ts +18 -0
- package/ui-components/forms/SettingsFormField.js +17 -0
- package/ui-components/forms/StatusAlert.d.ts +8 -0
- package/ui-components/forms/StatusAlert.js +7 -0
- package/ui-components/forms/index.d.ts +4 -0
- package/ui-components/forms/index.js +4 -0
- package/ui-components/index.d.ts +13 -0
- package/ui-components/index.js +18 -0
- package/ui-components/layout/CoreNavbar.d.ts +14 -0
- package/ui-components/layout/CoreNavbar.js +53 -0
- package/ui-components/layout/CoreSidebar.d.ts +28 -0
- package/ui-components/layout/CoreSidebar.js +62 -0
- package/ui-components/layout/LandingFooter.d.ts +28 -0
- package/ui-components/layout/LandingFooter.js +34 -0
- package/ui-components/layout/PageContainer.d.ts +8 -0
- package/ui-components/layout/PageContainer.js +17 -0
- package/ui-components/layout/PageHeader.d.ts +23 -0
- package/ui-components/layout/PageHeader.js +12 -0
- package/ui-components/layout/SettingsContainer.d.ts +10 -0
- package/ui-components/layout/SettingsContainer.js +9 -0
- package/ui-components/layout/ThemeToggle.d.ts +4 -0
- package/ui-components/layout/ThemeToggle.js +6 -0
- package/ui-components/layout/index.d.ts +7 -0
- package/ui-components/layout/index.js +7 -0
- package/ui-components/settings/GeneralInformationCard.d.ts +12 -0
- package/ui-components/settings/GeneralInformationCard.js +72 -0
- package/ui-components/settings/PasswordInformationCard.d.ts +10 -0
- package/ui-components/settings/PasswordInformationCard.js +167 -0
- package/ui-components/settings/SettingsPageHeader.d.ts +7 -0
- package/ui-components/settings/SettingsPageHeader.js +6 -0
- package/ui-components/settings/index.d.ts +3 -0
- package/ui-components/settings/index.js +3 -0
- package/ui-components/support/SupportModal.d.ts +15 -0
- package/ui-components/support/SupportModal.js +74 -0
- package/ui-components/support/index.d.ts +2 -0
- package/ui-components/support/index.js +1 -0
- package/ui-components/types/index.d.ts +39 -0
- package/ui-components/types/index.js +1 -0
- package/utils/index.d.ts +31 -0
- package/utils/index.js +38 -0
- package/utils/turnstile-config.d.ts +11 -0
- package/utils/turnstile-config.js +19 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState, } from 'react';
|
|
4
|
+
import { clearEntitySelection } from '../actions/entity-actions';
|
|
5
|
+
import { clearGraphSelection } from '../actions/graph-actions';
|
|
6
|
+
import { performLogoutCleanup } from '../auth-core/cleanup';
|
|
7
|
+
import { RoboSystemsAuthClient } from '../auth-core/client';
|
|
8
|
+
import { useTokenExpiryHandler } from '../auth-core/hooks';
|
|
9
|
+
import { getTimeUntilExpiry, getTokenStatus } from '../auth-core/token-storage';
|
|
10
|
+
// Configuration constants
|
|
11
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
12
|
+
const TOKEN_REFRESH_INTERVAL_MS = 25 * 60 * 1000; // 25 minutes (5 min before 30 min expiry)
|
|
13
|
+
const TOKEN_WARNING_CHECK_INTERVAL_MS = 30 * 1000; // 30 seconds - reduced for better battery life
|
|
14
|
+
const ACTIVITY_THROTTLE_MS = 1000; // 1 second
|
|
15
|
+
const HEARTBEAT_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes - server validation heartbeat
|
|
16
|
+
const REFRESH_COOLDOWN_MS = 60 * 1000; // 60 seconds - prevent duplicate background refreshes
|
|
17
|
+
const LOGOUT_TIMEOUT_MS = 10 * 1000; // 10 seconds - timeout for logout operation
|
|
18
|
+
const CACHE_VERSION = '1'; // Increment to invalidate all cached auth data
|
|
19
|
+
// Debug logging helper
|
|
20
|
+
const debugLog = (message, error) => {
|
|
21
|
+
if (process.env.NODE_ENV === 'development') {
|
|
22
|
+
console.debug(`[AuthProvider] ${message}`, error);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
// Storage error logging helper - logs to console.error for production visibility
|
|
26
|
+
const logStorageError = (operation, error) => {
|
|
27
|
+
const message = `[AuthProvider] Storage ${operation} failed - continuing with degraded UX`;
|
|
28
|
+
if (process.env.NODE_ENV === 'development') {
|
|
29
|
+
console.debug(message, error);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// In production, log to console.error so errors are visible in monitoring
|
|
33
|
+
console.error(message, error);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const AuthContext = createContext(null);
|
|
37
|
+
export function AuthProvider({ children, apiUrl = process.env.NEXT_PUBLIC_ROBOSYSTEMS_API_URL ||
|
|
38
|
+
'http://localhost:8000', }) {
|
|
39
|
+
const [user, setUser] = useState(null);
|
|
40
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
41
|
+
const [sessionWarning, setSessionWarning] = useState({ show: false, timeLeft: 0 });
|
|
42
|
+
const [authClient] = useState(() => new RoboSystemsAuthClient(apiUrl));
|
|
43
|
+
const lastActivity = useRef(Date.now());
|
|
44
|
+
const mounted = useRef(true);
|
|
45
|
+
const activityTimeoutRef = useRef(null);
|
|
46
|
+
const lastRefreshAttempt = useRef(0);
|
|
47
|
+
const refreshInProgress = useRef(false);
|
|
48
|
+
const isAuthenticated = user !== null;
|
|
49
|
+
const validateCachedUser = useCallback(async (userData) => {
|
|
50
|
+
try {
|
|
51
|
+
const user = await authClient.getCurrentUser();
|
|
52
|
+
if (user.id !== userData.id) {
|
|
53
|
+
// Cache is stale, update with fresh data
|
|
54
|
+
setUser(user);
|
|
55
|
+
if (typeof window !== 'undefined') {
|
|
56
|
+
try {
|
|
57
|
+
sessionStorage.setItem('auth_user_cache', JSON.stringify(Object.assign(Object.assign({}, user), { cached_at: Date.now(), cache_version: CACHE_VERSION })));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
// Storage error - continue without caching (graceful degradation)
|
|
61
|
+
logStorageError('write', error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
// User not authenticated anymore, clear cache
|
|
68
|
+
setUser(null);
|
|
69
|
+
if (typeof window !== 'undefined') {
|
|
70
|
+
try {
|
|
71
|
+
sessionStorage.removeItem('auth_user_cache');
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// Storage error - continue with graceful degradation
|
|
75
|
+
logStorageError('remove', error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}, [authClient]);
|
|
80
|
+
const checkSession = useCallback(async () => {
|
|
81
|
+
try {
|
|
82
|
+
const user = await authClient.getCurrentUser();
|
|
83
|
+
setUser(user);
|
|
84
|
+
// Cache the user data
|
|
85
|
+
if (typeof window !== 'undefined') {
|
|
86
|
+
try {
|
|
87
|
+
sessionStorage.setItem('auth_user_cache', JSON.stringify(Object.assign(Object.assign({}, user), { cached_at: Date.now(), cache_version: CACHE_VERSION })));
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// Storage error - continue without caching (graceful degradation)
|
|
91
|
+
logStorageError('write', error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
// User not authenticated, that's fine
|
|
97
|
+
setUser(null);
|
|
98
|
+
if (typeof window !== 'undefined') {
|
|
99
|
+
try {
|
|
100
|
+
sessionStorage.removeItem('auth_user_cache');
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
// Storage error - continue with graceful degradation
|
|
104
|
+
logStorageError('remove', error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
setIsLoading(false);
|
|
110
|
+
}
|
|
111
|
+
}, [authClient]);
|
|
112
|
+
const logout = useCallback(async (reason) => {
|
|
113
|
+
try {
|
|
114
|
+
await authClient.logout();
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
// Logout error - continue with local logout regardless
|
|
118
|
+
debugLog('Backend logout failed, continuing with local cleanup', error);
|
|
119
|
+
// Could show a non-blocking notification here if needed
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
// Clear the httpOnly graph/entity selection cookies server-side. These
|
|
123
|
+
// can't be removed by performLogoutCleanup() (which only touches
|
|
124
|
+
// JS-readable cookies), so the server actions are still required. Bound
|
|
125
|
+
// them with a timeout while the page is still authenticated: once the
|
|
126
|
+
// auth token is cleared, these server actions can stall, and awaiting
|
|
127
|
+
// them unconditionally previously hung logout() forever — leaving
|
|
128
|
+
// AuthGuard on a blank screen with no redirect.
|
|
129
|
+
await Promise.race([
|
|
130
|
+
Promise.allSettled([clearGraphSelection(), clearEntitySelection()]),
|
|
131
|
+
new Promise((resolve) => setTimeout(resolve, 2000)),
|
|
132
|
+
]);
|
|
133
|
+
// Clear auth client cache and local user/session state
|
|
134
|
+
authClient.clearAuthCache();
|
|
135
|
+
setUser(null);
|
|
136
|
+
setSessionWarning({ show: false, timeLeft: 0 });
|
|
137
|
+
// Perform comprehensive cleanup of all user-specific data
|
|
138
|
+
performLogoutCleanup();
|
|
139
|
+
debugLog('Logout cleanup completed');
|
|
140
|
+
// Always hard-redirect out of the authenticated area. A full-page
|
|
141
|
+
// navigation (not a client-side router.push) guarantees the
|
|
142
|
+
// authenticated tree tears down instead of AuthGuard flashing a blank
|
|
143
|
+
// screen once user state clears. A forced logout (reason set, e.g.
|
|
144
|
+
// session_expired) goes to the login page so it can explain why the
|
|
145
|
+
// session ended and let the user sign back in; a manual logout goes to
|
|
146
|
+
// the public homepage, since the user likely isn't trying to sign in
|
|
147
|
+
// again right away.
|
|
148
|
+
if (typeof window !== 'undefined') {
|
|
149
|
+
window.location.href = reason ? `/login?reason=${reason}` : '/';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}, [authClient]);
|
|
153
|
+
const refreshUser = useCallback(async () => {
|
|
154
|
+
try {
|
|
155
|
+
authClient.clearAuthCache();
|
|
156
|
+
const freshUser = await authClient.getCurrentUser();
|
|
157
|
+
setUser(freshUser);
|
|
158
|
+
if (typeof window !== 'undefined') {
|
|
159
|
+
try {
|
|
160
|
+
sessionStorage.setItem('auth_user_cache', JSON.stringify(Object.assign(Object.assign({}, freshUser), { cached_at: Date.now(), cache_version: CACHE_VERSION })));
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
logStorageError('write', error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return freshUser;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
debugLog('Failed to refresh user', error);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}, [authClient]);
|
|
173
|
+
const refreshSession = useCallback(async (force = false) => {
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
if (refreshInProgress.current) {
|
|
176
|
+
debugLog('Refresh already in progress, skipping duplicate attempt');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!force && now - lastRefreshAttempt.current < REFRESH_COOLDOWN_MS) {
|
|
180
|
+
debugLog(`Skipping refresh - too soon since last attempt (< ${REFRESH_COOLDOWN_MS / 1000}s)`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
refreshInProgress.current = true;
|
|
184
|
+
lastRefreshAttempt.current = now;
|
|
185
|
+
debugLog(force
|
|
186
|
+
? 'Starting user-initiated session refresh'
|
|
187
|
+
: 'Starting session refresh');
|
|
188
|
+
try {
|
|
189
|
+
const response = await authClient.refreshSession();
|
|
190
|
+
if (response.success) {
|
|
191
|
+
setUser(response.user);
|
|
192
|
+
// Cache the refreshed user data
|
|
193
|
+
if (typeof window !== 'undefined') {
|
|
194
|
+
try {
|
|
195
|
+
sessionStorage.setItem('auth_user_cache', JSON.stringify(Object.assign(Object.assign({}, response.user), { cached_at: Date.now(), cache_version: CACHE_VERSION })));
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
logStorageError('write', error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
debugLog('Session refresh successful');
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
throw new Error('Session refresh failed');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
refreshInProgress.current = false;
|
|
209
|
+
}
|
|
210
|
+
}, [authClient]);
|
|
211
|
+
// Set up global 401 error handler
|
|
212
|
+
useTokenExpiryHandler(logout);
|
|
213
|
+
// Check session on mount with caching
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
if (typeof window === 'undefined') {
|
|
216
|
+
return; // Skip during SSR
|
|
217
|
+
}
|
|
218
|
+
let cachedUser = null;
|
|
219
|
+
try {
|
|
220
|
+
cachedUser = sessionStorage.getItem('auth_user_cache');
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
// Storage access error - proceed without cache
|
|
224
|
+
}
|
|
225
|
+
if (cachedUser) {
|
|
226
|
+
try {
|
|
227
|
+
const userData = JSON.parse(cachedUser);
|
|
228
|
+
// Validate cache structure and version
|
|
229
|
+
if (!userData || typeof userData !== 'object' || !userData.id) {
|
|
230
|
+
throw new Error('Invalid cache structure');
|
|
231
|
+
}
|
|
232
|
+
// Check cache version - invalidate if version mismatch
|
|
233
|
+
if (userData.cache_version !== CACHE_VERSION) {
|
|
234
|
+
debugLog('Cache version mismatch, invalidating cache');
|
|
235
|
+
throw new Error('Cache version mismatch');
|
|
236
|
+
}
|
|
237
|
+
const cacheAge = Date.now() - (userData.cached_at || 0);
|
|
238
|
+
// If cache is fresh (< 5 minutes), use cached data
|
|
239
|
+
if (cacheAge < CACHE_TTL_MS) {
|
|
240
|
+
setUser(userData);
|
|
241
|
+
setIsLoading(false);
|
|
242
|
+
// Validate in background without blocking UI
|
|
243
|
+
validateCachedUser(userData);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
// Invalid cache, proceed with normal check
|
|
249
|
+
try {
|
|
250
|
+
sessionStorage.removeItem('auth_user_cache');
|
|
251
|
+
}
|
|
252
|
+
catch (removeError) {
|
|
253
|
+
// Storage error - continue silently
|
|
254
|
+
debugLog('sessionStorage remove failed during cache invalidation', removeError);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
checkSession();
|
|
259
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
260
|
+
}, []);
|
|
261
|
+
// Heartbeat - Server-side validation and background tab refresh
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
if (!isAuthenticated)
|
|
264
|
+
return;
|
|
265
|
+
let isMounted = true;
|
|
266
|
+
const performHeartbeat = async () => {
|
|
267
|
+
if (!isMounted)
|
|
268
|
+
return;
|
|
269
|
+
debugLog('Heartbeat: Checking server authentication status');
|
|
270
|
+
try {
|
|
271
|
+
// Make real HTTP call to validate session server-side
|
|
272
|
+
// This works even in background tabs (not throttled like timers)
|
|
273
|
+
await authClient.getCurrentUser();
|
|
274
|
+
if (!isMounted)
|
|
275
|
+
return;
|
|
276
|
+
debugLog('Heartbeat: Server validation successful');
|
|
277
|
+
// After successful server check, verify local token status
|
|
278
|
+
const tokenStatus = getTokenStatus();
|
|
279
|
+
const timeLeft = getTimeUntilExpiry();
|
|
280
|
+
// Proactively refresh if token is expiring soon
|
|
281
|
+
if (tokenStatus === 'warning' ||
|
|
282
|
+
tokenStatus === 'expired' ||
|
|
283
|
+
timeLeft < 5 * 60 * 1000) {
|
|
284
|
+
debugLog(`Heartbeat: Token needs refresh (status: ${tokenStatus}, time left: ${Math.floor(timeLeft / 1000)}s)`);
|
|
285
|
+
try {
|
|
286
|
+
await refreshSession();
|
|
287
|
+
if (!isMounted)
|
|
288
|
+
return;
|
|
289
|
+
debugLog('Heartbeat: Token refresh successful');
|
|
290
|
+
setSessionWarning({ show: false, timeLeft: 0 });
|
|
291
|
+
}
|
|
292
|
+
catch (refreshError) {
|
|
293
|
+
if (!isMounted)
|
|
294
|
+
return;
|
|
295
|
+
debugLog('Heartbeat: Token refresh failed', refreshError);
|
|
296
|
+
// Show warning if refresh failed but token not expired
|
|
297
|
+
if (timeLeft > 0) {
|
|
298
|
+
setSessionWarning({
|
|
299
|
+
show: true,
|
|
300
|
+
timeLeft: Math.ceil(timeLeft / 1000),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
if (!isMounted)
|
|
308
|
+
return;
|
|
309
|
+
debugLog('Heartbeat: Server validation failed - user may be logged out', error);
|
|
310
|
+
// Server says we're not authenticated - immediate logout
|
|
311
|
+
await logout('session_invalid');
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
// Regular heartbeat interval
|
|
315
|
+
const heartbeatInterval = setInterval(performHeartbeat, HEARTBEAT_INTERVAL_MS); // Every 5 minutes
|
|
316
|
+
// Also check immediately when tab regains focus
|
|
317
|
+
const handleFocus = () => {
|
|
318
|
+
if (!isMounted)
|
|
319
|
+
return;
|
|
320
|
+
debugLog('Tab regained focus - performing immediate heartbeat');
|
|
321
|
+
performHeartbeat();
|
|
322
|
+
};
|
|
323
|
+
const handleVisibilityChange = () => {
|
|
324
|
+
if (!isMounted)
|
|
325
|
+
return;
|
|
326
|
+
if (!document.hidden) {
|
|
327
|
+
debugLog('Tab became visible - performing immediate heartbeat');
|
|
328
|
+
performHeartbeat();
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
window.addEventListener('focus', handleFocus);
|
|
332
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
333
|
+
return () => {
|
|
334
|
+
isMounted = false;
|
|
335
|
+
clearInterval(heartbeatInterval);
|
|
336
|
+
window.removeEventListener('focus', handleFocus);
|
|
337
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
338
|
+
};
|
|
339
|
+
}, [isAuthenticated, authClient, refreshSession, logout]);
|
|
340
|
+
// Proactive token refresh BEFORE expiry (kept for immediate refresh needs)
|
|
341
|
+
useEffect(() => {
|
|
342
|
+
if (!isAuthenticated)
|
|
343
|
+
return;
|
|
344
|
+
let isMounted = true;
|
|
345
|
+
const refreshInterval = setInterval(async () => {
|
|
346
|
+
if (!isMounted)
|
|
347
|
+
return;
|
|
348
|
+
const tokenStatus = getTokenStatus();
|
|
349
|
+
// If token is in warning state or expired, refresh it
|
|
350
|
+
if (tokenStatus === 'warning' || tokenStatus === 'expired') {
|
|
351
|
+
debugLog(`Token status: ${tokenStatus}, attempting refresh`);
|
|
352
|
+
try {
|
|
353
|
+
await refreshSession();
|
|
354
|
+
if (!isMounted)
|
|
355
|
+
return;
|
|
356
|
+
setSessionWarning({ show: false, timeLeft: 0 });
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
if (!isMounted)
|
|
360
|
+
return;
|
|
361
|
+
debugLog('Token refresh failed', error);
|
|
362
|
+
// Show warning instead of silent logout
|
|
363
|
+
const timeLeft = getTimeUntilExpiry();
|
|
364
|
+
if (timeLeft > 0) {
|
|
365
|
+
setSessionWarning({
|
|
366
|
+
show: true,
|
|
367
|
+
timeLeft: Math.ceil(timeLeft / 1000),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
// Token is actually expired, need to re-authenticate
|
|
372
|
+
debugLog('Token expired, redirecting to login');
|
|
373
|
+
await logout('session_expired');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}, TOKEN_REFRESH_INTERVAL_MS); // Check every 25 minutes
|
|
378
|
+
// Also check token status more frequently and auto-refresh if needed
|
|
379
|
+
const warningInterval = setInterval(async () => {
|
|
380
|
+
if (!isMounted)
|
|
381
|
+
return;
|
|
382
|
+
const tokenStatus = getTokenStatus();
|
|
383
|
+
const timeLeft = getTimeUntilExpiry();
|
|
384
|
+
if (tokenStatus === 'warning' && timeLeft > 0) {
|
|
385
|
+
// Token is approaching expiry - try to auto-refresh before showing warning
|
|
386
|
+
debugLog('Token in warning state, attempting auto-refresh');
|
|
387
|
+
try {
|
|
388
|
+
await refreshSession();
|
|
389
|
+
if (!isMounted)
|
|
390
|
+
return;
|
|
391
|
+
debugLog('Auto-refresh successful, warning modal suppressed');
|
|
392
|
+
setSessionWarning({ show: false, timeLeft: 0 });
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
if (!isMounted)
|
|
396
|
+
return;
|
|
397
|
+
// Auto-refresh failed, show warning modal
|
|
398
|
+
debugLog('Auto-refresh failed, showing warning modal', error);
|
|
399
|
+
setSessionWarning({
|
|
400
|
+
show: true,
|
|
401
|
+
timeLeft: Math.ceil(timeLeft / 1000),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else if (tokenStatus === 'valid') {
|
|
406
|
+
if (!isMounted)
|
|
407
|
+
return;
|
|
408
|
+
setSessionWarning({ show: false, timeLeft: 0 });
|
|
409
|
+
}
|
|
410
|
+
}, TOKEN_WARNING_CHECK_INTERVAL_MS); // Check every 10 seconds
|
|
411
|
+
return () => {
|
|
412
|
+
isMounted = false;
|
|
413
|
+
clearInterval(refreshInterval);
|
|
414
|
+
clearInterval(warningInterval);
|
|
415
|
+
};
|
|
416
|
+
}, [isAuthenticated, refreshSession, logout]);
|
|
417
|
+
// Track user activity with throttling
|
|
418
|
+
useEffect(() => {
|
|
419
|
+
if (typeof window === 'undefined')
|
|
420
|
+
return;
|
|
421
|
+
const updateActivity = () => {
|
|
422
|
+
// Clear existing timeout to prevent race conditions
|
|
423
|
+
if (activityTimeoutRef.current) {
|
|
424
|
+
clearTimeout(activityTimeoutRef.current);
|
|
425
|
+
}
|
|
426
|
+
activityTimeoutRef.current = setTimeout(() => {
|
|
427
|
+
// Check if component is still mounted before updating
|
|
428
|
+
if (mounted.current) {
|
|
429
|
+
lastActivity.current = Date.now();
|
|
430
|
+
}
|
|
431
|
+
activityTimeoutRef.current = null;
|
|
432
|
+
}, ACTIVITY_THROTTLE_MS); // Update at most once per second
|
|
433
|
+
};
|
|
434
|
+
const options = { passive: true };
|
|
435
|
+
window.addEventListener('mousedown', updateActivity, options);
|
|
436
|
+
window.addEventListener('keydown', updateActivity, options);
|
|
437
|
+
window.addEventListener('scroll', updateActivity, options);
|
|
438
|
+
window.addEventListener('touchstart', updateActivity, options);
|
|
439
|
+
return () => {
|
|
440
|
+
if (activityTimeoutRef.current) {
|
|
441
|
+
clearTimeout(activityTimeoutRef.current);
|
|
442
|
+
}
|
|
443
|
+
window.removeEventListener('mousedown', updateActivity);
|
|
444
|
+
window.removeEventListener('keydown', updateActivity);
|
|
445
|
+
window.removeEventListener('scroll', updateActivity);
|
|
446
|
+
window.removeEventListener('touchstart', updateActivity);
|
|
447
|
+
};
|
|
448
|
+
}, []);
|
|
449
|
+
// Track component mount status
|
|
450
|
+
useEffect(() => {
|
|
451
|
+
mounted.current = true;
|
|
452
|
+
return () => {
|
|
453
|
+
mounted.current = false;
|
|
454
|
+
};
|
|
455
|
+
}, []);
|
|
456
|
+
const login = async (email, password) => {
|
|
457
|
+
const response = await authClient.login(email, password);
|
|
458
|
+
if (response.success) {
|
|
459
|
+
authClient.clearAuthCache(); // Clear request deduplication cache
|
|
460
|
+
setUser(response.user);
|
|
461
|
+
// Cache the user data
|
|
462
|
+
if (typeof window !== 'undefined') {
|
|
463
|
+
try {
|
|
464
|
+
sessionStorage.setItem('auth_user_cache', JSON.stringify(Object.assign(Object.assign({}, response.user), { cached_at: Date.now(), cache_version: CACHE_VERSION })));
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
// Storage error - continue without caching (graceful degradation)
|
|
468
|
+
logStorageError('write', error);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return response.user;
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
throw new Error(response.message || 'Login failed');
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
const register = async (email, password, name) => {
|
|
478
|
+
const response = await authClient.register(email, password, name);
|
|
479
|
+
if (response.success) {
|
|
480
|
+
authClient.clearAuthCache(); // Clear request deduplication cache
|
|
481
|
+
setUser(response.user);
|
|
482
|
+
// Cache the user data
|
|
483
|
+
if (typeof window !== 'undefined') {
|
|
484
|
+
try {
|
|
485
|
+
sessionStorage.setItem('auth_user_cache', JSON.stringify(Object.assign(Object.assign({}, response.user), { cached_at: Date.now(), cache_version: CACHE_VERSION })));
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
// Storage error - continue without caching (graceful degradation)
|
|
489
|
+
logStorageError('write', error);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return response.user;
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
throw new Error(response.message || 'Registration failed');
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const forgotPassword = async (email) => {
|
|
499
|
+
return authClient.forgotPassword(email);
|
|
500
|
+
};
|
|
501
|
+
const resetPassword = async (token, newPassword) => {
|
|
502
|
+
const result = await authClient.resetPassword(token, newPassword);
|
|
503
|
+
// If reset was successful and logged in automatically, fetch user
|
|
504
|
+
if (result.success) {
|
|
505
|
+
try {
|
|
506
|
+
const user = await authClient.getCurrentUser();
|
|
507
|
+
if (user) {
|
|
508
|
+
setUser(user);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
catch (_a) {
|
|
512
|
+
// User not logged in after reset, that's fine
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return result;
|
|
516
|
+
};
|
|
517
|
+
const validateResetToken = async (token) => {
|
|
518
|
+
return authClient.validateResetToken(token);
|
|
519
|
+
};
|
|
520
|
+
const verifyEmail = async (token) => {
|
|
521
|
+
const result = await authClient.verifyEmail(token);
|
|
522
|
+
// If verification was successful and logged in automatically, update user
|
|
523
|
+
if (result.success && result.user) {
|
|
524
|
+
setUser(result.user);
|
|
525
|
+
// Cache the user data
|
|
526
|
+
if (typeof window !== 'undefined') {
|
|
527
|
+
try {
|
|
528
|
+
sessionStorage.setItem('auth_user_cache', JSON.stringify(Object.assign(Object.assign({}, result.user), { cached_at: Date.now(), cache_version: CACHE_VERSION })));
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
// Storage error - continue without caching (graceful degradation)
|
|
532
|
+
logStorageError('write', error);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return result;
|
|
537
|
+
};
|
|
538
|
+
const resendVerificationEmail = async (email) => {
|
|
539
|
+
return authClient.resendVerificationEmail(email);
|
|
540
|
+
};
|
|
541
|
+
const value = {
|
|
542
|
+
user,
|
|
543
|
+
isLoading,
|
|
544
|
+
isAuthenticated,
|
|
545
|
+
sessionWarning,
|
|
546
|
+
login,
|
|
547
|
+
register,
|
|
548
|
+
logout,
|
|
549
|
+
refreshUser,
|
|
550
|
+
refreshSession,
|
|
551
|
+
forgotPassword,
|
|
552
|
+
resetPassword,
|
|
553
|
+
validateResetToken,
|
|
554
|
+
verifyEmail,
|
|
555
|
+
resendVerificationEmail,
|
|
556
|
+
};
|
|
557
|
+
return _jsx(AuthContext.Provider, { value: value, children: children });
|
|
558
|
+
}
|
|
559
|
+
export function useAuth() {
|
|
560
|
+
const context = useContext(AuthContext);
|
|
561
|
+
if (!context) {
|
|
562
|
+
throw new Error('useAuth must be used within an AuthProvider');
|
|
563
|
+
}
|
|
564
|
+
return context;
|
|
565
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function SessionWarningDialog(): import("react").JSX.Element;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useAuth } from './AuthProvider';
|
|
5
|
+
export function SessionWarningDialog() {
|
|
6
|
+
const { sessionWarning, refreshSession, logout } = useAuth();
|
|
7
|
+
const [countdown, setCountdown] = useState(0);
|
|
8
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (sessionWarning.show && sessionWarning.timeLeft > 0) {
|
|
11
|
+
setCountdown(sessionWarning.timeLeft);
|
|
12
|
+
}
|
|
13
|
+
}, [sessionWarning]);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!sessionWarning.show || countdown <= 0)
|
|
16
|
+
return;
|
|
17
|
+
const timer = setInterval(() => {
|
|
18
|
+
setCountdown((prev) => {
|
|
19
|
+
if (prev <= 1) {
|
|
20
|
+
// Time's up, logout
|
|
21
|
+
logout();
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
return prev - 1;
|
|
25
|
+
});
|
|
26
|
+
}, 1000);
|
|
27
|
+
return () => clearInterval(timer);
|
|
28
|
+
}, [sessionWarning.show, countdown, logout]);
|
|
29
|
+
const handleRefresh = useCallback(async () => {
|
|
30
|
+
setIsRefreshing(true);
|
|
31
|
+
try {
|
|
32
|
+
await refreshSession(true); // Force refresh to bypass cooldown
|
|
33
|
+
// Success - dialog will be hidden by AuthProvider
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error('Failed to refresh session:', error);
|
|
37
|
+
// Error handling - user will see the error
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
setIsRefreshing(false);
|
|
41
|
+
}
|
|
42
|
+
}, [refreshSession]);
|
|
43
|
+
const handleLogout = useCallback(async () => {
|
|
44
|
+
await logout();
|
|
45
|
+
}, [logout]);
|
|
46
|
+
if (!sessionWarning.show)
|
|
47
|
+
return null;
|
|
48
|
+
const minutes = Math.floor(countdown / 60);
|
|
49
|
+
const seconds = countdown % 60;
|
|
50
|
+
return (_jsx("div", { className: "bg-opacity-50 fixed inset-0 z-50 flex items-center justify-center bg-black", children: _jsxs("div", { className: "mx-4 max-w-md rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800", children: [_jsx("h2", { className: "mb-4 text-xl font-semibold text-gray-900 dark:text-white", children: "Session Expiring Soon" }), _jsxs("p", { className: "mb-6 text-gray-600 dark:text-gray-300", children: ["Your session will expire in", ' ', _jsx("span", { className: "font-semibold text-red-600 dark:text-red-400", children: minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s` }), ". Would you like to stay logged in?"] }), _jsxs("div", { className: "flex gap-3", children: [_jsx("button", { onClick: handleRefresh, disabled: isRefreshing, className: "bg-primary-700 hover:bg-primary-800 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800 flex-1 rounded-lg px-4 py-2 text-white focus:ring-4 focus:outline-none disabled:opacity-50", children: isRefreshing ? 'Refreshing...' : 'Stay Logged In' }), _jsx("button", { onClick: handleLogout, className: "flex-1 rounded-lg border border-gray-300 bg-white px-4 py-2 text-gray-700 hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-700", children: "Logout" })] })] }) }));
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AuthUser } from '../auth-core/types';
|
|
3
|
+
export interface SignInFormProps {
|
|
4
|
+
onSuccess?: (user: AuthUser) => void;
|
|
5
|
+
onRedirect?: (url: string) => void;
|
|
6
|
+
redirectTo?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
apiUrl: string;
|
|
9
|
+
enableSSO?: boolean;
|
|
10
|
+
currentApp?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Map a login failure to a user-facing message. A connectivity failure (the
|
|
14
|
+
* request never reached the server — `fetch` throws a `TypeError`) must NOT be
|
|
15
|
+
* reported as bad credentials: that sends users to reset a password that is
|
|
16
|
+
* actually correct. Reached-server auth rejection (401/403, or an empty/invalid
|
|
17
|
+
* auth body) stays "Invalid email or password"; 5xx gets its own message.
|
|
18
|
+
*/
|
|
19
|
+
export declare function loginErrorMessage(error: unknown): string;
|
|
20
|
+
export declare function SignInForm({ onSuccess, onRedirect, redirectTo, className, apiUrl, enableSSO, currentApp, }: SignInFormProps): React.JSX.Element;
|