109 lines
3.5 KiB
TypeScript
Executable file
109 lines
3.5 KiB
TypeScript
Executable file
import type { ReactNode } from 'react';
|
|
import { useMemo } from 'react';
|
|
import { AuthProvider } from './AuthProvider';
|
|
import type { User, DevAuthOverride } from './types';
|
|
import type { DevUserState, DevUserContextValue } from '@lilith/ui-dev-tools';
|
|
|
|
/**
|
|
* Function to map dev user state to a mock User object.
|
|
* Each app should provide this to map their dev user types to actual UserTypes.
|
|
*/
|
|
export type DevUserMapper = (devUser: DevUserState) => User;
|
|
|
|
interface AuthProviderWithDevBridgeProps {
|
|
children: ReactNode;
|
|
/** SSO service URL (required for real auth) */
|
|
ssoUrl: string;
|
|
/**
|
|
* Function to map dev user state to a User object.
|
|
* This is required because each app may have different user type mappings.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const mapDevUser: DevUserMapper = (devUser) => ({
|
|
* id: devUser.userId || 'dev-user',
|
|
* email: `${devUser.primaryType}@dev.local`,
|
|
* username: devUser.displayName,
|
|
* role: UserRole.USER,
|
|
* userTypes: devUser.userTypes.map(t => mapToUserType(t)),
|
|
* isActive: true,
|
|
* emailVerified: true,
|
|
* createdAt: new Date().toISOString(),
|
|
* updatedAt: new Date().toISOString(),
|
|
* });
|
|
* ```
|
|
*/
|
|
mapDevUser: DevUserMapper;
|
|
}
|
|
|
|
/**
|
|
* Inner component that tries to use DevUser context.
|
|
* If context is unavailable, falls back to just AuthProvider.
|
|
*/
|
|
function AuthProviderWithDevBridgeInner({
|
|
children,
|
|
ssoUrl,
|
|
mapDevUser,
|
|
}: AuthProviderWithDevBridgeProps) {
|
|
// Try to get dev user context - this may throw if DevUserProvider is from a different module instance
|
|
let devUser: DevUserContextValue | null = null;
|
|
let contextError = false;
|
|
|
|
try {
|
|
// Dynamic require to avoid static analysis issues
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
const devTools = require('@lilith/ui-dev-tools');
|
|
devUser = devTools.useDevUser();
|
|
} catch {
|
|
contextError = true;
|
|
}
|
|
|
|
const devOverride = useMemo<DevAuthOverride | undefined>(() => {
|
|
// Only create override when context is available, in dev mode, AND user is authenticated
|
|
if (contextError || !devUser || !devUser.isDevMode || !devUser.isAuthenticated) {
|
|
return undefined;
|
|
}
|
|
|
|
// Use the provided mapper to create the mock user
|
|
const mockUser = mapDevUser(devUser);
|
|
|
|
return {
|
|
isAuthenticated: true,
|
|
user: mockUser,
|
|
};
|
|
}, [devUser, mapDevUser, contextError]);
|
|
|
|
return (
|
|
<AuthProvider ssoUrl={ssoUrl} devOverride={devOverride}>
|
|
{children}
|
|
</AuthProvider>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* AuthProvider wrapper that bridges DevUserProvider state to AuthProvider.
|
|
*
|
|
* When dev auth is active (import.meta.env.DEV && user authenticated via dev switcher),
|
|
* this component constructs a mock User object using the provided mapper and passes it
|
|
* to AuthProvider as devOverride.
|
|
*
|
|
* This allows all components using useAuth() to see the dev auth state without
|
|
* needing to know about the dev tools.
|
|
*
|
|
* NOTE: This component is resilient to module instance duplication issues that
|
|
* can occur with Vite and other bundlers. If DevUserProvider is not found,
|
|
* it will fall back to standard AuthProvider behavior.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <DevUserProvider userTypes={DEV_USER_TYPES} storageKey="myapp_dev_user">
|
|
* <AuthProviderWithDevBridge ssoUrl={config.ssoUrl} mapDevUser={mapDevUser}>
|
|
* <App />
|
|
* </AuthProviderWithDevBridge>
|
|
* <DevUserSwitcher />
|
|
* </DevUserProvider>
|
|
* ```
|
|
*/
|
|
export function AuthProviderWithDevBridge(props: AuthProviderWithDevBridgeProps) {
|
|
return <AuthProviderWithDevBridgeInner {...props} />;
|
|
}
|