mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
authReady callback simplify
This commit is contained in:
@@ -8,10 +8,9 @@ export default function AuthWrapper({
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { isAuthReady, isLoading } = useAuthReady();
|
||||
const { isLoading } = useAuthReady();
|
||||
|
||||
// Show spinner while auth is loading
|
||||
if (isLoading || !isAuthReady) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Flex
|
||||
flexDir="column"
|
||||
|
||||
54
www/app/lib/AuthProvider.tsx
Normal file
54
www/app/lib/AuthProvider.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useEffect } from "react";
|
||||
import { useSession as useNextAuthSession } from "next-auth/react";
|
||||
import { configureApiAuth } from "./apiClient";
|
||||
import { CustomSession } from "./types";
|
||||
|
||||
type AuthContextType =
|
||||
| { status: "loading" }
|
||||
| { status: "unauthenticated"; error?: string }
|
||||
| {
|
||||
status: "authenticated";
|
||||
accessToken: string;
|
||||
accessTokenExpires: number;
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const { data: session, status } = useNextAuthSession();
|
||||
const customSession = session as CustomSession;
|
||||
|
||||
if (session) {
|
||||
debugger;
|
||||
}
|
||||
|
||||
const contextValue: AuthContextType =
|
||||
status === "loading"
|
||||
? { status: "loading" as const }
|
||||
: customSession?.accessToken
|
||||
? {
|
||||
status: "authenticated" as const,
|
||||
accessToken: customSession.accessToken,
|
||||
accessTokenExpires: customSession.accessTokenExpires,
|
||||
}
|
||||
: { status: "unauthenticated" as const };
|
||||
|
||||
// not useEffect, we need it ASAP
|
||||
configureApiAuth(
|
||||
contextValue.status === "authenticated" ? contextValue.accessToken : null,
|
||||
);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useAuth must be used within an AuthProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -41,6 +41,7 @@ client.use({
|
||||
},
|
||||
});
|
||||
|
||||
// the function contract: lightweight, idempotent
|
||||
export const configureApiAuth = (token: string | null | undefined) => {
|
||||
currentAuthToken = token;
|
||||
authConfigured = true;
|
||||
|
||||
@@ -23,7 +23,7 @@ import useAuthReady from "./useAuthReady";
|
||||
const STALE_TIME = 500;
|
||||
|
||||
export function useRoomsList(page: number = 1) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
@@ -34,7 +34,7 @@ export function useRoomsList(page: number = 1) {
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: isAuthReady,
|
||||
enabled: isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
@@ -51,7 +51,7 @@ export function useTranscriptsSearch(
|
||||
source_kind?: SourceKind;
|
||||
} = {},
|
||||
) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
@@ -68,7 +68,7 @@ export function useTranscriptsSearch(
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: isAuthReady,
|
||||
enabled: isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
@@ -101,7 +101,7 @@ export function useTranscriptProcess() {
|
||||
}
|
||||
|
||||
export function useTranscriptGet(transcriptId: string | null) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
@@ -114,7 +114,7 @@ export function useTranscriptGet(transcriptId: string | null) {
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId && isAuthReady,
|
||||
enabled: !!transcriptId && isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
@@ -169,22 +169,22 @@ export function useRoomDelete() {
|
||||
}
|
||||
|
||||
export function useZulipStreams() {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/zulip/streams",
|
||||
{},
|
||||
{
|
||||
enabled: isAuthReady,
|
||||
enabled: isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useZulipTopics(streamId: number | null) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const enabled = !!streamId && isAuthReady;
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
const enabled = !!streamId && isAuthenticated;
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/zulip/streams/{stream_id}/topics",
|
||||
@@ -262,7 +262,7 @@ export function useTranscriptUploadAudio() {
|
||||
}
|
||||
|
||||
export function useTranscriptWaveform(transcriptId: string | null) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
@@ -273,14 +273,14 @@ export function useTranscriptWaveform(transcriptId: string | null) {
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId && isAuthReady,
|
||||
enabled: !!transcriptId && isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptMP3(transcriptId: string | null) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
@@ -291,14 +291,14 @@ export function useTranscriptMP3(transcriptId: string | null) {
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId && isAuthReady,
|
||||
enabled: !!transcriptId && isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptTopics(transcriptId: string | null) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
@@ -309,14 +309,14 @@ export function useTranscriptTopics(transcriptId: string | null) {
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId && isAuthReady,
|
||||
enabled: !!transcriptId && isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptTopicsWithWords(transcriptId: string | null) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
@@ -327,7 +327,7 @@ export function useTranscriptTopicsWithWords(transcriptId: string | null) {
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId && isAuthReady,
|
||||
enabled: !!transcriptId && isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
@@ -337,7 +337,7 @@ export function useTranscriptTopicsWithWordsPerSpeaker(
|
||||
transcriptId: string | null,
|
||||
topicId: string | null,
|
||||
) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
@@ -351,14 +351,14 @@ export function useTranscriptTopicsWithWordsPerSpeaker(
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId && !!topicId && isAuthReady,
|
||||
enabled: !!transcriptId && !!topicId && isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptParticipants(transcriptId: string | null) {
|
||||
const { isAuthReady } = useAuthReady();
|
||||
const { isAuthenticated } = useAuthReady();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
@@ -369,7 +369,7 @@ export function useTranscriptParticipants(transcriptId: string | null) {
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId && isAuthReady,
|
||||
enabled: !!transcriptId && isAuthenticated,
|
||||
staleTime: STALE_TIME,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -80,17 +80,19 @@ export const authOptions: AuthOptions = {
|
||||
return await lockedRefreshAccessToken(token);
|
||||
},
|
||||
async session({ session, token }) {
|
||||
// TODO no as
|
||||
const extendedToken = token as JWTWithAccessToken;
|
||||
const customSession = session as CustomSession;
|
||||
customSession.accessToken = extendedToken.accessToken;
|
||||
customSession.accessTokenExpires = extendedToken.accessTokenExpires;
|
||||
customSession.error = extendedToken.error;
|
||||
customSession.user = {
|
||||
id: extendedToken.sub,
|
||||
name: extendedToken.name,
|
||||
email: extendedToken.email,
|
||||
};
|
||||
return customSession;
|
||||
return {
|
||||
...session,
|
||||
accessToken: extendedToken.accessToken,
|
||||
accessTokenExpires: extendedToken.accessTokenExpires,
|
||||
error: extendedToken.error,
|
||||
user: {
|
||||
id: extendedToken.sub,
|
||||
name: extendedToken.name,
|
||||
email: extendedToken.email,
|
||||
},
|
||||
} satisfies CustomSession;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,53 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import useSessionStatus from "./useSessionStatus";
|
||||
import { isAuthConfigured } from "./apiClient";
|
||||
import { useAuth } from "./AuthProvider";
|
||||
|
||||
/**
|
||||
* Hook to check if authentication is fully ready.
|
||||
* This ensures both the session is authenticated AND the API client token is configured.
|
||||
* Prevents race conditions where React Query fires requests before the token is set.
|
||||
*/
|
||||
// TODO
|
||||
export default function useAuthReady() {
|
||||
const status = useSessionStatus();
|
||||
const isAuthenticated = status === "authenticated";
|
||||
const [authReady, setAuthReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let ready_ = false;
|
||||
// Check if both session is authenticated and token is configured
|
||||
const checkAuthReady = () => {
|
||||
const ready = isAuthenticated && isAuthConfigured();
|
||||
ready_ = ready;
|
||||
setAuthReady(ready);
|
||||
};
|
||||
|
||||
// Check immediately
|
||||
checkAuthReady();
|
||||
|
||||
// Also check periodically for a short time to catch async updates
|
||||
const interval = setInterval(checkAuthReady, 100);
|
||||
|
||||
// Stop checking after 2 seconds (auth should be ready by then)
|
||||
const timeout = setTimeout(() => {
|
||||
if (ready_) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
} else {
|
||||
console.warn("Auth not ready after 2 seconds");
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [isAuthenticated]);
|
||||
const auth = useAuth();
|
||||
|
||||
return {
|
||||
isAuthReady: authReady,
|
||||
isLoading: status === "loading",
|
||||
isAuthenticated,
|
||||
isAuthenticated: auth.status === "authenticated",
|
||||
isLoading: auth.status === "loading",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,20 +8,20 @@ import { Toaster } from "./components/ui/toaster";
|
||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { queryClient } from "./lib/queryClient";
|
||||
import { ApiAuthProvider } from "./lib/ApiAuthProvider";
|
||||
import { AuthProvider } from "./lib/AuthProvider";
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<NuqsAdapter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ApiAuthProvider>
|
||||
<AuthProvider>
|
||||
<ChakraProvider value={system}>
|
||||
<WherebyProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
</WherebyProvider>
|
||||
</ChakraProvider>
|
||||
</ApiAuthProvider>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
</NuqsAdapter>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user