dograh/ui/src/lib/auth/providers/StackProviderWrapper.tsx

137 lines
4.3 KiB
TypeScript
Raw Normal View History

2025-09-09 14:37:32 +05:30
'use client';
import { StackClientApp,StackProvider, StackTheme, useUser as useStackUser } from '@stackframe/stack';
import React, { useEffect, useState } from 'react';
2025-09-09 19:10:18 +05:30
import logger from '@/lib/logger';
2025-09-09 14:37:32 +05:30
import { StackAuthService } from '../services';
import type { AuthUser } from '../types';
import { AuthContext } from './AuthProvider';
2025-09-09 19:10:18 +05:30
// Create a singleton StackClientApp instance to prevent multiple initializations
let stackClientAppInstance: StackClientApp<true, string> | null = null;
function getStackClientApp(): StackClientApp<true, string> {
if (!stackClientAppInstance) {
logger.debug('[StackProviderWrapper] Creating singleton StackClientApp instance');
stackClientAppInstance = new StackClientApp({
tokenStore: "nextjs-cookie",
urls: {
afterSignIn: "/after-sign-in"
}
});
}
return stackClientAppInstance;
}
2025-09-09 14:37:32 +05:30
interface StackProviderWrapperProps {
service: StackAuthService;
children: React.ReactNode;
}
// Stack-specific context provider that uses the useUser hook
function StackAuthContextProvider({ service, children }: { service: StackAuthService; children: React.ReactNode }) {
2025-09-09 19:10:18 +05:30
const renderCount = React.useRef(0);
const lastUserId = React.useRef<string | undefined>(undefined);
renderCount.current++;
logger.debug(`[StackAuthContextProvider] Render #${renderCount.current} - Starting`);
2025-09-09 14:37:32 +05:30
const stackUser = useStackUser(); // Always call the hook
2025-09-09 19:10:18 +05:30
const [isInitialized, setIsInitialized] = useState(false);
// Track if user actually changed
const userChanged = lastUserId.current !== stackUser?.id;
if (userChanged) {
lastUserId.current = stackUser?.id;
}
logger.debug(`[StackAuthContextProvider] Render #${renderCount.current} - stackUser:`, {
hasUser: !!stackUser,
userId: stackUser?.id,
isInitialized,
userChanged
});
2025-09-09 14:37:32 +05:30
useEffect(() => {
2025-09-09 19:10:18 +05:30
// Only log and update if user actually changed
if (!userChanged && isInitialized) {
return;
}
logger.debug('[StackAuthContextProvider] useEffect triggered (user changed)', {
hasUser: !!stackUser,
userId: stackUser?.id,
isInitialized,
isStackAuthService: service instanceof StackAuthService
});
// Only update the service once when user becomes available
if (!isInitialized && service instanceof StackAuthService && stackUser) {
logger.debug('[StackAuthContextProvider] Setting user instance in service', {
userId: stackUser.id
});
2025-09-09 14:37:32 +05:30
service.setUserInstance(stackUser);
2025-09-09 19:10:18 +05:30
setIsInitialized(true);
2025-09-09 14:37:32 +05:30
}
2025-09-09 19:10:18 +05:30
}, [service, stackUser, isInitialized, userChanged]);
const getAccessToken = React.useCallback(() => {
logger.debug('[StackAuthContextProvider] getAccessToken called');
return service.getAccessToken();
}, [service]);
// Stabilize the context value to prevent unnecessary re-renders
const contextValue = React.useMemo(() => {
const isAuth = service.isAuthenticated();
// IMPORTANT: Stay in loading state until service is initialized (has user set)
// Even if stackUser exists, we're still loading until setUserInstance is called
const loadingState = !isInitialized;
2025-09-09 14:37:32 +05:30
2025-09-09 19:10:18 +05:30
const value = {
service,
user: stackUser as AuthUser, // Pass the actual Stack CurrentUser
isAuthenticated: isAuth,
loading: loadingState, // Loading until service is initialized
getAccessToken,
provider: service.getProviderName(),
};
2025-09-09 14:37:32 +05:30
2025-09-09 19:10:18 +05:30
logger.debug('[StackAuthContextProvider] Context value created', {
isAuthenticated: isAuth,
loading: loadingState,
hasUser: !!value.user,
userId: stackUser?.id,
isInitialized,
provider: value.provider,
serviceHasUser: isAuth
});
return value;
}, [service, stackUser, isInitialized, getAccessToken]);
2025-09-09 14:37:32 +05:30
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
}
export function StackProviderWrapper({ service, children }: StackProviderWrapperProps) {
2025-09-09 19:10:18 +05:30
logger.debug('[StackProviderWrapper] Rendering wrapper');
// Use the singleton instance
const stackClientApp = getStackClientApp();
2025-09-09 14:37:32 +05:30
return (
<StackProvider app={stackClientApp}>
<StackTheme>
<StackAuthContextProvider service={service}>
{children}
</StackAuthContextProvider>
</StackTheme>
</StackProvider>
);
}