@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.
Files changed (249) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -0
  3. package/actions/entity-actions.d.ts +8 -0
  4. package/actions/entity-actions.js +16 -0
  5. package/actions/graph-actions.d.ts +3 -0
  6. package/actions/graph-actions.js +13 -0
  7. package/auth-components/AppSwitcher.d.ts +6 -0
  8. package/auth-components/AppSwitcher.js +28 -0
  9. package/auth-components/AuthGuard.d.ts +7 -0
  10. package/auth-components/AuthGuard.js +31 -0
  11. package/auth-components/AuthProvider.d.ts +9 -0
  12. package/auth-components/AuthProvider.js +565 -0
  13. package/auth-components/SessionWarningDialog.d.ts +1 -0
  14. package/auth-components/SessionWarningDialog.js +51 -0
  15. package/auth-components/SignInForm.d.ts +20 -0
  16. package/auth-components/SignInForm.js +139 -0
  17. package/auth-components/SignUpForm.d.ts +15 -0
  18. package/auth-components/SignUpForm.js +167 -0
  19. package/auth-components/TurnstileWidget.d.ts +99 -0
  20. package/auth-components/TurnstileWidget.js +224 -0
  21. package/auth-components/index.d.ts +6 -0
  22. package/auth-components/index.js +6 -0
  23. package/auth-core/cleanup.d.ts +44 -0
  24. package/auth-core/cleanup.js +157 -0
  25. package/auth-core/client.d.ts +121 -0
  26. package/auth-core/client.js +767 -0
  27. package/auth-core/config.d.ts +19 -0
  28. package/auth-core/config.js +56 -0
  29. package/auth-core/hooks.d.ts +19 -0
  30. package/auth-core/hooks.js +63 -0
  31. package/auth-core/index.d.ts +6 -0
  32. package/auth-core/index.js +6 -0
  33. package/auth-core/sso.d.ts +42 -0
  34. package/auth-core/sso.js +281 -0
  35. package/auth-core/token-storage.d.ts +58 -0
  36. package/auth-core/token-storage.js +177 -0
  37. package/auth-core/types.d.ts +145 -0
  38. package/auth-core/types.js +1 -0
  39. package/components/EntitySelector.d.ts +44 -0
  40. package/components/EntitySelector.js +64 -0
  41. package/components/EntitySelectorCore.d.ts +45 -0
  42. package/components/EntitySelectorCore.js +67 -0
  43. package/components/GraphSelectorCore.d.ts +21 -0
  44. package/components/GraphSelectorCore.js +39 -0
  45. package/components/PageLayout.d.ts +8 -0
  46. package/components/PageLayout.js +5 -0
  47. package/components/RepositoryGuard.d.ts +68 -0
  48. package/components/RepositoryGuard.js +91 -0
  49. package/components/console/ConsoleContent.d.ts +4 -0
  50. package/components/console/ConsoleContent.js +612 -0
  51. package/components/console/ConsoleMarkdown.d.ts +11 -0
  52. package/components/console/ConsoleMarkdown.js +49 -0
  53. package/components/console/ProgressiveText.d.ts +6 -0
  54. package/components/console/ProgressiveText.js +21 -0
  55. package/components/console/graphAwareConfig.d.ts +70 -0
  56. package/components/console/graphAwareConfig.js +411 -0
  57. package/components/console/index.d.ts +6 -0
  58. package/components/console/index.js +4 -0
  59. package/components/console/types.d.ts +84 -0
  60. package/components/console/types.js +1 -0
  61. package/components/graph-filters.d.ts +75 -0
  62. package/components/graph-filters.js +111 -0
  63. package/components/index.d.ts +10 -0
  64. package/components/index.js +9 -0
  65. package/components/repositories/ActiveSubscriptions.d.ts +26 -0
  66. package/components/repositories/ActiveSubscriptions.js +103 -0
  67. package/components/repositories/BrowseRepositories.d.ts +11 -0
  68. package/components/repositories/BrowseRepositories.js +184 -0
  69. package/components/repositories/index.d.ts +2 -0
  70. package/components/repositories/index.js +2 -0
  71. package/components/search/SearchContent.d.ts +4 -0
  72. package/components/search/SearchContent.js +168 -0
  73. package/components/search/index.d.ts +2 -0
  74. package/components/search/index.js +1 -0
  75. package/components/search/types.d.ts +15 -0
  76. package/components/search/types.js +1 -0
  77. package/contexts/entity-context.d.ts +28 -0
  78. package/contexts/entity-context.js +89 -0
  79. package/contexts/graph-context.d.ts +27 -0
  80. package/contexts/graph-context.js +128 -0
  81. package/contexts/index.d.ts +5 -0
  82. package/contexts/index.js +5 -0
  83. package/contexts/org-context.d.ts +25 -0
  84. package/contexts/org-context.js +64 -0
  85. package/contexts/service-offerings-context.d.ts +45 -0
  86. package/contexts/service-offerings-context.js +98 -0
  87. package/contexts/sidebar-context.d.ts +18 -0
  88. package/contexts/sidebar-context.js +44 -0
  89. package/hooks/index.d.ts +6 -0
  90. package/hooks/index.js +6 -0
  91. package/hooks/use-api-error.d.ts +3 -0
  92. package/hooks/use-api-error.js +24 -0
  93. package/hooks/use-media-query.d.ts +1 -0
  94. package/hooks/use-media-query.js +21 -0
  95. package/hooks/use-streaming-query.d.ts +22 -0
  96. package/hooks/use-streaming-query.js +142 -0
  97. package/hooks/use-toast.d.ts +37 -0
  98. package/hooks/use-toast.js +117 -0
  99. package/hooks/use-user-limits.d.ts +12 -0
  100. package/hooks/use-user-limits.js +73 -0
  101. package/hooks/use-user.d.ts +11 -0
  102. package/hooks/use-user.js +39 -0
  103. package/index.d.ts +29 -0
  104. package/index.js +77 -0
  105. package/lib/entity-cookie.d.ts +10 -0
  106. package/lib/entity-cookie.js +38 -0
  107. package/lib/graph-cookie.d.ts +11 -0
  108. package/lib/graph-cookie.js +62 -0
  109. package/lib/graph-tiers.d.ts +41 -0
  110. package/lib/graph-tiers.js +107 -0
  111. package/lib/index.d.ts +3 -0
  112. package/lib/index.js +3 -0
  113. package/lib/sidebar-cookie.d.ts +11 -0
  114. package/lib/sidebar-cookie.js +42 -0
  115. package/library/colors.d.ts +4 -0
  116. package/library/colors.js +41 -0
  117. package/library/components/ClassificationPicker.d.ts +8 -0
  118. package/library/components/ClassificationPicker.js +55 -0
  119. package/library/components/ElementBrowser.d.ts +11 -0
  120. package/library/components/ElementBrowser.js +106 -0
  121. package/library/components/ElementDetail.d.ts +7 -0
  122. package/library/components/ElementDetail.js +97 -0
  123. package/library/components/LibraryHierarchy.d.ts +13 -0
  124. package/library/components/LibraryHierarchy.js +380 -0
  125. package/library/components/TaxonomySidebar.d.ts +6 -0
  126. package/library/components/TaxonomySidebar.js +45 -0
  127. package/library/index.d.ts +8 -0
  128. package/library/index.js +7 -0
  129. package/package.json +37 -0
  130. package/research/CoverageCard.d.ts +12 -0
  131. package/research/CoverageCard.js +15 -0
  132. package/research/CoverageGrid.d.ts +6 -0
  133. package/research/CoverageGrid.js +9 -0
  134. package/research/CoverageHistory.d.ts +5 -0
  135. package/research/CoverageHistory.js +7 -0
  136. package/research/ResearchArticle.d.ts +15 -0
  137. package/research/ResearchArticle.js +26 -0
  138. package/research/catalog.d.ts +14 -0
  139. package/research/catalog.js +42 -0
  140. package/research/index.d.ts +6 -0
  141. package/research/index.js +9 -0
  142. package/research/types.d.ts +38 -0
  143. package/research/types.js +4 -0
  144. package/task-monitoring/hooks.d.ts +26 -0
  145. package/task-monitoring/hooks.js +126 -0
  146. package/task-monitoring/index.d.ts +5 -0
  147. package/task-monitoring/index.js +7 -0
  148. package/task-monitoring/operationErrors.d.ts +82 -0
  149. package/task-monitoring/operationErrors.js +251 -0
  150. package/task-monitoring/operationHooks.d.ts +117 -0
  151. package/task-monitoring/operationHooks.js +536 -0
  152. package/task-monitoring/operationMonitor.d.ts +78 -0
  153. package/task-monitoring/operationMonitor.js +200 -0
  154. package/task-monitoring/operationTypes.d.ts +236 -0
  155. package/task-monitoring/operationTypes.js +60 -0
  156. package/task-monitoring/taskMonitor.d.ts +14 -0
  157. package/task-monitoring/taskMonitor.js +132 -0
  158. package/task-monitoring/types.d.ts +31 -0
  159. package/task-monitoring/types.js +1 -0
  160. package/theme/flowbite-theme.d.ts +758 -0
  161. package/theme/flowbite-theme.js +765 -0
  162. package/theme/index.d.ts +1 -0
  163. package/theme/index.js +1 -0
  164. package/types/entity.d.ts +19 -0
  165. package/types/index.d.ts +2 -0
  166. package/types/index.js +1 -0
  167. package/types/user.d.ts +6 -0
  168. package/ui-components/ConfirmModal.d.ts +41 -0
  169. package/ui-components/ConfirmModal.js +19 -0
  170. package/ui-components/EmptyState.d.ts +22 -0
  171. package/ui-components/EmptyState.js +10 -0
  172. package/ui-components/LoadingState.d.ts +27 -0
  173. package/ui-components/LoadingState.js +12 -0
  174. package/ui-components/Logo.d.ts +28 -0
  175. package/ui-components/Logo.js +95 -0
  176. package/ui-components/Spinner.d.ts +37 -0
  177. package/ui-components/Spinner.js +48 -0
  178. package/ui-components/StatCard.d.ts +21 -0
  179. package/ui-components/StatCard.js +10 -0
  180. package/ui-components/api-keys/ApiKeyDisplay.d.ts +13 -0
  181. package/ui-components/api-keys/ApiKeyDisplay.js +7 -0
  182. package/ui-components/api-keys/ApiKeyTable.d.ts +9 -0
  183. package/ui-components/api-keys/ApiKeyTable.js +17 -0
  184. package/ui-components/api-keys/ApiKeysCard.d.ts +6 -0
  185. package/ui-components/api-keys/ApiKeysCard.js +97 -0
  186. package/ui-components/api-keys/CreateApiKeyModal.d.ts +11 -0
  187. package/ui-components/api-keys/CreateApiKeyModal.js +47 -0
  188. package/ui-components/api-keys/SecureApiKeyField.d.ts +13 -0
  189. package/ui-components/api-keys/SecureApiKeyField.js +64 -0
  190. package/ui-components/api-keys/index.d.ts +5 -0
  191. package/ui-components/api-keys/index.js +5 -0
  192. package/ui-components/chat/ChatHeader.d.ts +12 -0
  193. package/ui-components/chat/ChatHeader.js +5 -0
  194. package/ui-components/chat/ChatInputArea.d.ts +16 -0
  195. package/ui-components/chat/ChatInputArea.js +9 -0
  196. package/ui-components/chat/ChatMessage.d.ts +7 -0
  197. package/ui-components/chat/ChatMessage.js +24 -0
  198. package/ui-components/chat/DeepResearchToggle.d.ts +8 -0
  199. package/ui-components/chat/DeepResearchToggle.js +13 -0
  200. package/ui-components/chat/index.d.ts +9 -0
  201. package/ui-components/chat/index.js +8 -0
  202. package/ui-components/chat/types.d.ts +23 -0
  203. package/ui-components/chat/types.js +51 -0
  204. package/ui-components/forms/PasswordRequirements.d.ts +10 -0
  205. package/ui-components/forms/PasswordRequirements.js +15 -0
  206. package/ui-components/forms/SettingsCard.d.ts +11 -0
  207. package/ui-components/forms/SettingsCard.js +5 -0
  208. package/ui-components/forms/SettingsFormField.d.ts +18 -0
  209. package/ui-components/forms/SettingsFormField.js +17 -0
  210. package/ui-components/forms/StatusAlert.d.ts +8 -0
  211. package/ui-components/forms/StatusAlert.js +7 -0
  212. package/ui-components/forms/index.d.ts +4 -0
  213. package/ui-components/forms/index.js +4 -0
  214. package/ui-components/index.d.ts +13 -0
  215. package/ui-components/index.js +18 -0
  216. package/ui-components/layout/CoreNavbar.d.ts +14 -0
  217. package/ui-components/layout/CoreNavbar.js +53 -0
  218. package/ui-components/layout/CoreSidebar.d.ts +28 -0
  219. package/ui-components/layout/CoreSidebar.js +62 -0
  220. package/ui-components/layout/LandingFooter.d.ts +28 -0
  221. package/ui-components/layout/LandingFooter.js +34 -0
  222. package/ui-components/layout/PageContainer.d.ts +8 -0
  223. package/ui-components/layout/PageContainer.js +17 -0
  224. package/ui-components/layout/PageHeader.d.ts +23 -0
  225. package/ui-components/layout/PageHeader.js +12 -0
  226. package/ui-components/layout/SettingsContainer.d.ts +10 -0
  227. package/ui-components/layout/SettingsContainer.js +9 -0
  228. package/ui-components/layout/ThemeToggle.d.ts +4 -0
  229. package/ui-components/layout/ThemeToggle.js +6 -0
  230. package/ui-components/layout/index.d.ts +7 -0
  231. package/ui-components/layout/index.js +7 -0
  232. package/ui-components/settings/GeneralInformationCard.d.ts +12 -0
  233. package/ui-components/settings/GeneralInformationCard.js +72 -0
  234. package/ui-components/settings/PasswordInformationCard.d.ts +10 -0
  235. package/ui-components/settings/PasswordInformationCard.js +167 -0
  236. package/ui-components/settings/SettingsPageHeader.d.ts +7 -0
  237. package/ui-components/settings/SettingsPageHeader.js +6 -0
  238. package/ui-components/settings/index.d.ts +3 -0
  239. package/ui-components/settings/index.js +3 -0
  240. package/ui-components/support/SupportModal.d.ts +15 -0
  241. package/ui-components/support/SupportModal.js +74 -0
  242. package/ui-components/support/index.d.ts +2 -0
  243. package/ui-components/support/index.js +1 -0
  244. package/ui-components/types/index.d.ts +39 -0
  245. package/ui-components/types/index.js +1 -0
  246. package/utils/index.d.ts +31 -0
  247. package/utils/index.js +38 -0
  248. package/utils/turnstile-config.d.ts +11 -0
  249. 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;