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;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const { isAuthReady, isLoading } = useAuthReady();
|
const { isLoading } = useAuthReady();
|
||||||
|
|
||||||
// Show spinner while auth is loading
|
if (isLoading) {
|
||||||
if (isLoading || !isAuthReady) {
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
flexDir="column"
|
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) => {
|
export const configureApiAuth = (token: string | null | undefined) => {
|
||||||
currentAuthToken = token;
|
currentAuthToken = token;
|
||||||
authConfigured = true;
|
authConfigured = true;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import useAuthReady from "./useAuthReady";
|
|||||||
const STALE_TIME = 500;
|
const STALE_TIME = 500;
|
||||||
|
|
||||||
export function useRoomsList(page: number = 1) {
|
export function useRoomsList(page: number = 1) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
@@ -34,7 +34,7 @@ export function useRoomsList(page: number = 1) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: isAuthReady,
|
enabled: isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -51,7 +51,7 @@ export function useTranscriptsSearch(
|
|||||||
source_kind?: SourceKind;
|
source_kind?: SourceKind;
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
@@ -68,7 +68,7 @@ export function useTranscriptsSearch(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: isAuthReady,
|
enabled: isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -101,7 +101,7 @@ export function useTranscriptProcess() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useTranscriptGet(transcriptId: string | null) {
|
export function useTranscriptGet(transcriptId: string | null) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
@@ -114,7 +114,7 @@ export function useTranscriptGet(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId && isAuthReady,
|
enabled: !!transcriptId && isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -169,22 +169,22 @@ export function useRoomDelete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useZulipStreams() {
|
export function useZulipStreams() {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
"/v1/zulip/streams",
|
"/v1/zulip/streams",
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
enabled: isAuthReady,
|
enabled: isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useZulipTopics(streamId: number | null) {
|
export function useZulipTopics(streamId: number | null) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
const enabled = !!streamId && isAuthReady;
|
const enabled = !!streamId && isAuthenticated;
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
"/v1/zulip/streams/{stream_id}/topics",
|
"/v1/zulip/streams/{stream_id}/topics",
|
||||||
@@ -262,7 +262,7 @@ export function useTranscriptUploadAudio() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useTranscriptWaveform(transcriptId: string | null) {
|
export function useTranscriptWaveform(transcriptId: string | null) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
@@ -273,14 +273,14 @@ export function useTranscriptWaveform(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId && isAuthReady,
|
enabled: !!transcriptId && isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTranscriptMP3(transcriptId: string | null) {
|
export function useTranscriptMP3(transcriptId: string | null) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
@@ -291,14 +291,14 @@ export function useTranscriptMP3(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId && isAuthReady,
|
enabled: !!transcriptId && isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTranscriptTopics(transcriptId: string | null) {
|
export function useTranscriptTopics(transcriptId: string | null) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
@@ -309,14 +309,14 @@ export function useTranscriptTopics(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId && isAuthReady,
|
enabled: !!transcriptId && isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTranscriptTopicsWithWords(transcriptId: string | null) {
|
export function useTranscriptTopicsWithWords(transcriptId: string | null) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
@@ -327,7 +327,7 @@ export function useTranscriptTopicsWithWords(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId && isAuthReady,
|
enabled: !!transcriptId && isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -337,7 +337,7 @@ export function useTranscriptTopicsWithWordsPerSpeaker(
|
|||||||
transcriptId: string | null,
|
transcriptId: string | null,
|
||||||
topicId: string | null,
|
topicId: string | null,
|
||||||
) {
|
) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
@@ -351,14 +351,14 @@ export function useTranscriptTopicsWithWordsPerSpeaker(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId && !!topicId && isAuthReady,
|
enabled: !!transcriptId && !!topicId && isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTranscriptParticipants(transcriptId: string | null) {
|
export function useTranscriptParticipants(transcriptId: string | null) {
|
||||||
const { isAuthReady } = useAuthReady();
|
const { isAuthenticated } = useAuthReady();
|
||||||
|
|
||||||
return $api.useQuery(
|
return $api.useQuery(
|
||||||
"get",
|
"get",
|
||||||
@@ -369,7 +369,7 @@ export function useTranscriptParticipants(transcriptId: string | null) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!transcriptId && isAuthReady,
|
enabled: !!transcriptId && isAuthenticated,
|
||||||
staleTime: STALE_TIME,
|
staleTime: STALE_TIME,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -80,17 +80,19 @@ export const authOptions: AuthOptions = {
|
|||||||
return await lockedRefreshAccessToken(token);
|
return await lockedRefreshAccessToken(token);
|
||||||
},
|
},
|
||||||
async session({ session, token }) {
|
async session({ session, token }) {
|
||||||
|
// TODO no as
|
||||||
const extendedToken = token as JWTWithAccessToken;
|
const extendedToken = token as JWTWithAccessToken;
|
||||||
const customSession = session as CustomSession;
|
return {
|
||||||
customSession.accessToken = extendedToken.accessToken;
|
...session,
|
||||||
customSession.accessTokenExpires = extendedToken.accessTokenExpires;
|
accessToken: extendedToken.accessToken,
|
||||||
customSession.error = extendedToken.error;
|
accessTokenExpires: extendedToken.accessTokenExpires,
|
||||||
customSession.user = {
|
error: extendedToken.error,
|
||||||
id: extendedToken.sub,
|
user: {
|
||||||
name: extendedToken.name,
|
id: extendedToken.sub,
|
||||||
email: extendedToken.email,
|
name: extendedToken.name,
|
||||||
};
|
email: extendedToken.email,
|
||||||
return customSession;
|
},
|
||||||
|
} satisfies CustomSession;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,53 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useAuth } from "./AuthProvider";
|
||||||
import useSessionStatus from "./useSessionStatus";
|
|
||||||
import { isAuthConfigured } from "./apiClient";
|
|
||||||
|
|
||||||
/**
|
// TODO
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
export default function useAuthReady() {
|
export default function useAuthReady() {
|
||||||
const status = useSessionStatus();
|
const auth = useAuth();
|
||||||
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]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isAuthReady: authReady,
|
isAuthenticated: auth.status === "authenticated",
|
||||||
isLoading: status === "loading",
|
isLoading: auth.status === "loading",
|
||||||
isAuthenticated,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,20 @@ import { Toaster } from "./components/ui/toaster";
|
|||||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { queryClient } from "./lib/queryClient";
|
import { queryClient } from "./lib/queryClient";
|
||||||
import { ApiAuthProvider } from "./lib/ApiAuthProvider";
|
import { AuthProvider } from "./lib/AuthProvider";
|
||||||
|
|
||||||
export function Providers({ children }: { children: React.ReactNode }) {
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<NuqsAdapter>
|
<NuqsAdapter>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<ApiAuthProvider>
|
<AuthProvider>
|
||||||
<ChakraProvider value={system}>
|
<ChakraProvider value={system}>
|
||||||
<WherebyProvider>
|
<WherebyProvider>
|
||||||
{children}
|
{children}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</WherebyProvider>
|
</WherebyProvider>
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
</ApiAuthProvider>
|
</AuthProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</NuqsAdapter>
|
</NuqsAdapter>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user