@jmruthers/pace-core 0.5.185 → 0.5.186
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/dist/{PublicPageProvider-BABf6JCh.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +4 -2
- package/dist/{chunk-STTZQK2I.js → chunk-DAGICKHT.js} +7 -5
- package/dist/chunk-DAGICKHT.js.map +1 -0
- package/dist/{chunk-AISXLWGZ.js → chunk-GRIQLQ52.js} +2 -2
- package/dist/{chunk-HC67NW5K.js → chunk-HDCUMOOI.js} +125 -47
- package/dist/chunk-HDCUMOOI.js.map +1 -0
- package/dist/{chunk-OKI34GZD.js → chunk-OALXJH4Y.js} +2 -2
- package/dist/{chunk-MX3EIJGQ.js → chunk-TC7D3CR3.js} +86 -7
- package/dist/chunk-TC7D3CR3.js.map +1 -0
- package/dist/{chunk-IXSNYUCT.js → chunk-UQWSHFVX.js} +1 -1
- package/dist/chunk-UQWSHFVX.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +3 -3
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/{file-reference-BjR39ktt.d.ts → file-reference-PRTSLxKx.d.ts} +3 -0
- package/dist/hooks.d.ts +49 -5
- package/dist/hooks.js +6 -4
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +9 -8
- package/dist/index.js.map +1 -1
- package/dist/rbac/index.d.ts +1 -1
- package/dist/rbac/index.js +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CvnC3d-e.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +2 -2
- package/dist/utils.d.ts +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +33 -9
- package/docs/api/interfaces/FileUploadProps.md +36 -14
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +2 -2
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +17 -17
- package/docs/api-reference/components.md +26 -12
- package/docs/implementation-guides/file-reference-system.md +24 -2
- package/docs/implementation-guides/file-upload-storage.md +9 -1
- package/package.json +1 -1
- package/scripts/check-pace-core-compliance.js +512 -0
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +7 -1
- package/src/components/Header/Header.tsx +2 -5
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +134 -1
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFileReference.test.ts +1 -0
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +3 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +58 -4
- package/src/utils/file-reference/index.ts +12 -2
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +68 -0
- package/dist/chunk-HC67NW5K.js.map +0 -1
- package/dist/chunk-IXSNYUCT.js.map +0 -1
- package/dist/chunk-MX3EIJGQ.js.map +0 -1
- package/dist/chunk-STTZQK2I.js.map +0 -1
- /package/dist/{chunk-AISXLWGZ.js.map → chunk-GRIQLQ52.js.map} +0 -0
- /package/dist/{chunk-OKI34GZD.js.map → chunk-OALXJH4Y.js.map} +0 -0
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
* - LoadingSpinner - Loading state UI
|
|
69
69
|
*/
|
|
70
70
|
|
|
71
|
-
import React, { useMemo } from 'react';
|
|
71
|
+
import React, { useMemo, useEffect, useRef, useState } from 'react';
|
|
72
72
|
import { Navigate, Outlet } from 'react-router-dom';
|
|
73
73
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
74
74
|
import { useSessionRestoration } from '../../hooks/useSessionRestoration';
|
|
@@ -77,6 +77,7 @@ import { LoadingSpinner } from '../LoadingSpinner/LoadingSpinner';
|
|
|
77
77
|
import { SessionRestorationLoader } from '../SessionRestorationLoader';
|
|
78
78
|
import { Alert, AlertDescription, AlertTitle } from '../Alert/Alert';
|
|
79
79
|
import { logger } from '../../utils/core/logger';
|
|
80
|
+
import { usePreventTabReload } from '../../hooks/usePreventTabReload';
|
|
80
81
|
|
|
81
82
|
export interface ProtectedRouteProps {
|
|
82
83
|
/**
|
|
@@ -148,6 +149,96 @@ export function ProtectedRoute({
|
|
|
148
149
|
|
|
149
150
|
const sessionRestoration = useSessionRestoration();
|
|
150
151
|
|
|
152
|
+
// Prevent full page reloads when switching tabs (handles bfcache and visibility changes)
|
|
153
|
+
usePreventTabReload({ enabled: true, gracePeriodMs: 2000 });
|
|
154
|
+
|
|
155
|
+
// Track if user was previously authenticated to prevent redirects during session refresh
|
|
156
|
+
const wasAuthenticatedRef = useRef(false);
|
|
157
|
+
const [shouldRedirect, setShouldRedirect] = useState(false);
|
|
158
|
+
const tabJustBecameVisibleRef = useRef(false);
|
|
159
|
+
|
|
160
|
+
// Track authentication state to detect when user was previously logged in
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (isAuthenticated) {
|
|
163
|
+
wasAuthenticatedRef.current = true;
|
|
164
|
+
setShouldRedirect(false);
|
|
165
|
+
tabJustBecameVisibleRef.current = false; // Clear visibility flag when authenticated
|
|
166
|
+
}
|
|
167
|
+
}, [isAuthenticated]);
|
|
168
|
+
|
|
169
|
+
// Handle tab visibility changes - prevent immediate redirects when tab becomes visible
|
|
170
|
+
// This prevents the page from refreshing when switching back to the tab
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (typeof document === 'undefined') return;
|
|
173
|
+
|
|
174
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
175
|
+
let wasHidden = document.hidden;
|
|
176
|
+
|
|
177
|
+
const handleVisibilityChange = () => {
|
|
178
|
+
const isNowVisible = !document.hidden;
|
|
179
|
+
|
|
180
|
+
// When tab becomes visible, immediately prevent redirects and give session refresh time
|
|
181
|
+
if (isNowVisible && wasHidden) {
|
|
182
|
+
// Tab just became visible - immediately prevent redirects
|
|
183
|
+
if (!isAuthenticated && wasAuthenticatedRef.current) {
|
|
184
|
+
tabJustBecameVisibleRef.current = true;
|
|
185
|
+
setShouldRedirect(false); // Immediately clear redirect flag
|
|
186
|
+
|
|
187
|
+
// Clear any existing timeout
|
|
188
|
+
if (timeoutId) {
|
|
189
|
+
clearTimeout(timeoutId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Wait a bit to see if session refresh completes
|
|
193
|
+
timeoutId = setTimeout(() => {
|
|
194
|
+
// Only allow redirect if still not authenticated after delay
|
|
195
|
+
tabJustBecameVisibleRef.current = false;
|
|
196
|
+
// Use a function to get the latest state
|
|
197
|
+
setShouldRedirect((prev) => {
|
|
198
|
+
// Only set to true if we're still not authenticated
|
|
199
|
+
// This will be checked again in the render logic
|
|
200
|
+
return prev;
|
|
201
|
+
});
|
|
202
|
+
}, 2000); // 2 second grace period for session refresh
|
|
203
|
+
}
|
|
204
|
+
} else if (!isNowVisible) {
|
|
205
|
+
// Tab became hidden - clear the visibility flag
|
|
206
|
+
tabJustBecameVisibleRef.current = false;
|
|
207
|
+
if (timeoutId) {
|
|
208
|
+
clearTimeout(timeoutId);
|
|
209
|
+
timeoutId = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
wasHidden = !isNowVisible;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Check initial state - if tab is visible and user appears logged out, give grace period
|
|
217
|
+
if (!document.hidden && !isAuthenticated && wasAuthenticatedRef.current) {
|
|
218
|
+
tabJustBecameVisibleRef.current = true;
|
|
219
|
+
setShouldRedirect(false);
|
|
220
|
+
timeoutId = setTimeout(() => {
|
|
221
|
+
tabJustBecameVisibleRef.current = false;
|
|
222
|
+
}, 2000);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
226
|
+
return () => {
|
|
227
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
228
|
+
if (timeoutId) {
|
|
229
|
+
clearTimeout(timeoutId);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}, [isAuthenticated]);
|
|
233
|
+
|
|
234
|
+
// Reset redirect flag when authenticated
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (isAuthenticated) {
|
|
237
|
+
setShouldRedirect(false);
|
|
238
|
+
tabJustBecameVisibleRef.current = false;
|
|
239
|
+
}
|
|
240
|
+
}, [isAuthenticated]);
|
|
241
|
+
|
|
151
242
|
const isRestoringSession = useMemo(() => {
|
|
152
243
|
return sessionRestoration.isRestoring &&
|
|
153
244
|
!sessionRestoration.restorationComplete &&
|
|
@@ -182,13 +273,55 @@ export function ProtectedRoute({
|
|
|
182
273
|
}
|
|
183
274
|
|
|
184
275
|
// Redirect to login if not authenticated
|
|
276
|
+
// Priority order:
|
|
277
|
+
// 1. If session restoration has timed out or errored → redirect immediately (even if loading)
|
|
278
|
+
// 2. If user was never authenticated → redirect immediately (even if loading)
|
|
279
|
+
// 3. If tab just became visible → show loading (prevent redirect during grace period)
|
|
280
|
+
// 4. If we've confirmed they should redirect (after visibility change grace period) → redirect
|
|
281
|
+
// 5. Otherwise, if loading → show loading spinner (session might be refreshing)
|
|
282
|
+
// 6. Otherwise → redirect (user is not authenticated and not loading)
|
|
185
283
|
if (!isAuthenticated) {
|
|
284
|
+
// Session restoration timeout/error always redirects immediately
|
|
186
285
|
if (sessionRestoration.hasTimedOut || sessionRestoration.restorationError) {
|
|
187
286
|
logger.warn('ProtectedRoute', 'Session restoration failed, redirecting to login', {
|
|
188
287
|
timedOut: sessionRestoration.hasTimedOut,
|
|
189
288
|
error: sessionRestoration.restorationError?.message
|
|
190
289
|
});
|
|
290
|
+
return <Navigate to={loginPath} replace />;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// User was never authenticated → redirect immediately
|
|
294
|
+
if (!wasAuthenticatedRef.current) {
|
|
295
|
+
return <Navigate to={loginPath} replace />;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Tab just became visible - show loading to prevent redirect during grace period
|
|
299
|
+
// Also check document visibility state directly as a fallback
|
|
300
|
+
const isTabVisible = typeof document !== 'undefined' && !document.hidden;
|
|
301
|
+
if (tabJustBecameVisibleRef.current || (isTabVisible && wasAuthenticatedRef.current && isLoading)) {
|
|
302
|
+
return loadingFallback || (
|
|
303
|
+
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
|
|
304
|
+
<LoadingSpinner />
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
191
307
|
}
|
|
308
|
+
|
|
309
|
+
// We've confirmed redirect after grace period → redirect
|
|
310
|
+
if (shouldRedirect) {
|
|
311
|
+
return <Navigate to={loginPath} replace />;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// User was authenticated before but now appears logged out
|
|
315
|
+
// Show loading state while we wait for session refresh (unless we're not loading)
|
|
316
|
+
if (isLoading) {
|
|
317
|
+
return loadingFallback || (
|
|
318
|
+
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
|
|
319
|
+
<LoadingSpinner />
|
|
320
|
+
</div>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Not loading and not authenticated → redirect
|
|
192
325
|
return <Navigate to={loginPath} replace />;
|
|
193
326
|
}
|
|
194
327
|
|
package/src/hooks/index.ts
CHANGED
|
@@ -64,6 +64,9 @@ export type { UseStorageOptions, UseStorageReturn } from './useStorage';
|
|
|
64
64
|
// === PUBLIC DATA ACCESS HOOKS ===
|
|
65
65
|
export * from './public';
|
|
66
66
|
|
|
67
|
+
// === PAGE LIFECYCLE HOOKS ===
|
|
68
|
+
export { usePreventTabReload } from './usePreventTabReload';
|
|
69
|
+
export type { UsePreventTabReloadOptions } from './usePreventTabReload';
|
|
67
70
|
|
|
68
71
|
// RBAC Hooks - Use @jmruthers/pace-core/rbac instead
|
|
69
72
|
// Note: RBAC functionality has been moved to the dedicated RBAC module
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file usePreventTabReload Hook
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Hooks
|
|
5
|
+
* @since 0.6.0
|
|
6
|
+
*
|
|
7
|
+
* Prevents full page reloads when switching browser tabs.
|
|
8
|
+
* Handles browser back-forward cache (bfcache) restoration and tab visibility changes.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useRef } from 'react';
|
|
12
|
+
|
|
13
|
+
export interface UsePreventTabReloadOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Whether to enable the prevention logic
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Grace period in milliseconds to wait after tab becomes visible
|
|
22
|
+
* before allowing any potential reloads
|
|
23
|
+
* @default 2000
|
|
24
|
+
*/
|
|
25
|
+
gracePeriodMs?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Hook to prevent full page reloads when switching browser tabs.
|
|
30
|
+
*
|
|
31
|
+
* This hook handles:
|
|
32
|
+
* - Browser back-forward cache (bfcache) restoration
|
|
33
|
+
* - Tab visibility changes
|
|
34
|
+
* - Prevents unwanted page reloads when returning to a tab
|
|
35
|
+
*
|
|
36
|
+
* @param options - Configuration options
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```tsx
|
|
40
|
+
* import { usePreventTabReload } from '@jmruthers/pace-core';
|
|
41
|
+
*
|
|
42
|
+
* function MyComponent() {
|
|
43
|
+
* usePreventTabReload();
|
|
44
|
+
* // Component code...
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function usePreventTabReload(options: UsePreventTabReloadOptions = {}): void {
|
|
49
|
+
const { enabled = true, gracePeriodMs = 2000 } = options;
|
|
50
|
+
const isRestoringFromCacheRef = useRef(false);
|
|
51
|
+
const gracePeriodTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!enabled || typeof window === 'undefined') return;
|
|
55
|
+
|
|
56
|
+
// Handle pageshow event - detects when page is restored from bfcache
|
|
57
|
+
const handlePageShow = (event: PageTransitionEvent) => {
|
|
58
|
+
// If page was restored from bfcache, prevent any reload behavior
|
|
59
|
+
if (event.persisted) {
|
|
60
|
+
isRestoringFromCacheRef.current = true;
|
|
61
|
+
|
|
62
|
+
// Clear any existing timeout
|
|
63
|
+
if (gracePeriodTimeoutRef.current) {
|
|
64
|
+
clearTimeout(gracePeriodTimeoutRef.current);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Set grace period to prevent reloads
|
|
68
|
+
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
69
|
+
isRestoringFromCacheRef.current = false;
|
|
70
|
+
}, gracePeriodMs);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Handle visibility changes - when tab becomes visible
|
|
75
|
+
const handleVisibilityChange = () => {
|
|
76
|
+
if (!document.hidden) {
|
|
77
|
+
// Tab just became visible - set flag to prevent reloads
|
|
78
|
+
isRestoringFromCacheRef.current = true;
|
|
79
|
+
|
|
80
|
+
// Clear any existing timeout
|
|
81
|
+
if (gracePeriodTimeoutRef.current) {
|
|
82
|
+
clearTimeout(gracePeriodTimeoutRef.current);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Set grace period
|
|
86
|
+
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
87
|
+
isRestoringFromCacheRef.current = false;
|
|
88
|
+
}, gracePeriodMs);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Add event listeners
|
|
93
|
+
window.addEventListener('pageshow', handlePageShow);
|
|
94
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
95
|
+
|
|
96
|
+
return () => {
|
|
97
|
+
window.removeEventListener('pageshow', handlePageShow);
|
|
98
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
99
|
+
|
|
100
|
+
if (gracePeriodTimeoutRef.current) {
|
|
101
|
+
clearTimeout(gracePeriodTimeoutRef.current);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}, [enabled, gracePeriodMs]);
|
|
105
|
+
}
|
|
106
|
+
|
|
@@ -219,7 +219,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
219
219
|
'cake_meal', 'cake_mealtype', 'pace_person', 'pace_member',
|
|
220
220
|
// SECURITY: Phase 3A additions - medical and personal data
|
|
221
221
|
'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
|
|
222
|
-
'pace_consent', 'pace_contact', '
|
|
222
|
+
'pace_consent', 'pace_contact', 'pace_identification', 'pace_identification_type', 'pace_qualification',
|
|
223
223
|
'form_responses', 'form_response_values', 'forms',
|
|
224
224
|
// SECURITY: Phase 3B additions - remaining critical tables
|
|
225
225
|
'invoice', 'line_item', 'credit_balance', 'payment_method',
|
|
@@ -379,7 +379,7 @@ export function useSecureDataAccess(): SecureDataAccessReturn {
|
|
|
379
379
|
'cake_meal', 'cake_mealtype', 'pace_person', 'pace_member',
|
|
380
380
|
// SECURITY: Phase 3A additions - medical and personal data
|
|
381
381
|
'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',
|
|
382
|
-
'pace_consent', 'pace_contact', '
|
|
382
|
+
'pace_consent', 'pace_contact', 'pace_identification', 'pace_identification_type', 'pace_qualification',
|
|
383
383
|
'form_responses', 'form_response_values', 'forms',
|
|
384
384
|
// SECURITY: Phase 3B additions - remaining critical tables
|
|
385
385
|
'invoice', 'line_item', 'credit_balance', 'payment_method',
|