mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 12:19:06 +00:00
fix: unattended component reload due to session changes (#407)
* fix: unattended component reload due to session changes When the session is updated, status goes back to loading then authenticated or unauthenticated. session.accessTokenExpires may also be updated. This triggered various component refresh at unattented times, and brake the user experience. By splitting to only what's needed with memoization, it will prevent unattented refresh. * review * review: change syntax
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
import { GetTranscript } from "../../api";
|
||||
import Pagination from "./pagination";
|
||||
@@ -13,6 +12,7 @@ import { formatTimeMs } from "../../lib/time";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { useError } from "../../(errors)/errorContext";
|
||||
import { FaEllipsisVertical } from "react-icons/fa6";
|
||||
import useSessionUser from "../../lib/useSessionUser";
|
||||
import {
|
||||
Flex,
|
||||
Spinner,
|
||||
@@ -45,7 +45,7 @@ import { ExpandableText } from "../../lib/expandableText";
|
||||
export default function TranscriptBrowser() {
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const { loading, response, refetch } = useTranscriptList(page);
|
||||
const { data: session } = useSession();
|
||||
const userName = useSessionUser().name;
|
||||
const [deletionLoading, setDeletionLoading] = useState(false);
|
||||
const api = useApi();
|
||||
const { setError } = useError();
|
||||
@@ -136,8 +136,8 @@ export default function TranscriptBrowser() {
|
||||
flexWrap={"wrap-reverse"}
|
||||
mt={4}
|
||||
>
|
||||
{session?.user?.name ? (
|
||||
<Heading size="md">{session?.user?.name}'s Meetings</Heading>
|
||||
{userName ? (
|
||||
<Heading size="md">{userName}'s Meetings</Heading>
|
||||
) : (
|
||||
<Heading size="md">Your meetings</Heading>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useRouter } from "next/navigation";
|
||||
import useCreateTranscript from "../createTranscript";
|
||||
import SelectSearch from "react-select-search";
|
||||
import { supportedLanguages } from "../../../supportedLanguages";
|
||||
import { useSession } from "next-auth/react";
|
||||
import useSessionStatus from "../../../lib/useSessionStatus";
|
||||
import { featureEnabled } from "../../../domainContext";
|
||||
import { signIn } from "next-auth/react";
|
||||
import {
|
||||
@@ -44,9 +44,7 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
const TranscriptCreate = () => {
|
||||
const router = useRouter();
|
||||
const { status } = useSession();
|
||||
const sessionReady = status !== "loading";
|
||||
const isAuthenticated = status === "authenticated";
|
||||
const { isLoading, isAuthenticated } = useSessionStatus();
|
||||
const requireLogin = featureEnabled("requireLogin");
|
||||
|
||||
const [name, setName] = useState<string>("");
|
||||
@@ -141,7 +139,7 @@ const TranscriptCreate = () => {
|
||||
</Flex>
|
||||
<Flex flexDir="column" h="full" flexBasis="1" flexGrow={1}>
|
||||
<Center>
|
||||
{!sessionReady ? (
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : requireLogin && !isAuthenticated ? (
|
||||
<Button onClick={() => signIn("authentik")} colorScheme="blue">
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import { FaShare } from "react-icons/fa";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { useSession } from "next-auth/react";
|
||||
import useSessionUser from "../../lib/useSessionUser";
|
||||
import { CustomSession } from "../../lib/types";
|
||||
import { Select } from "chakra-react-select";
|
||||
import ShareLink from "./shareLink";
|
||||
@@ -70,9 +70,7 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
||||
setShareLoading(false);
|
||||
};
|
||||
|
||||
const { data: session } = useSession();
|
||||
const customSession = session as CustomSession;
|
||||
const userId = customSession?.user?.id;
|
||||
const userId = useSessionUser().id;
|
||||
|
||||
useEffect(() => {
|
||||
setIsOwner(!!(requireLogin && userId === props.transcriptResponse.user_id));
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
import { useSession, signOut, signIn } from "next-auth/react";
|
||||
import { signOut, signIn } from "next-auth/react";
|
||||
import useSessionStatus from "../lib/useSessionStatus";
|
||||
import { Spinner, Link } from "@chakra-ui/react";
|
||||
|
||||
export default function UserInfo() {
|
||||
const { status } = useSession();
|
||||
const sessionReady = status !== "loading";
|
||||
const isAuthenticated = status === "authenticated";
|
||||
const { isLoading, isAuthenticated } = useSessionStatus();
|
||||
|
||||
return !sessionReady ? (
|
||||
return isLoading ? (
|
||||
<Spinner size="xs" thickness="1px" className="mx-3" />
|
||||
) : !isAuthenticated ? (
|
||||
<Link
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Box, Button, Text, VStack, HStack } from "@chakra-ui/react";
|
||||
import useRoomMeeting from "./useRoomMeeting";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSession } from "next-auth/react";
|
||||
import useSessionStatus from "../lib/useSessionStatus";
|
||||
|
||||
export type RoomDetails = {
|
||||
params: {
|
||||
@@ -18,9 +18,7 @@ export default function Room(details: RoomDetails) {
|
||||
const roomName = details.params.roomName;
|
||||
const meeting = useRoomMeeting(roomName);
|
||||
const router = useRouter();
|
||||
const { status } = useSession();
|
||||
const sessionReady = status !== "loading";
|
||||
const isAuthenticated = status === "authenticated";
|
||||
const { isLoading, isAuthenticated } = useSessionStatus();
|
||||
|
||||
const [consentGiven, setConsentGiven] = useState<boolean | null>(null);
|
||||
|
||||
@@ -37,16 +35,16 @@ export default function Room(details: RoomDetails) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionReady || !isAuthenticated || !roomUrl) return;
|
||||
if (isLoading || !isAuthenticated || !roomUrl) return;
|
||||
|
||||
wherebyRef.current?.addEventListener("leave", handleLeave);
|
||||
|
||||
return () => {
|
||||
wherebyRef.current?.removeEventListener("leave", handleLeave);
|
||||
};
|
||||
}, [handleLeave, roomUrl, sessionReady, isAuthenticated]);
|
||||
}, [handleLeave, roomUrl, isLoading, isAuthenticated]);
|
||||
|
||||
if (!isAuthenticated && !consentGiven) {
|
||||
if (isLoading && !isAuthenticated && !consentGiven) {
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
|
||||
@@ -3,38 +3,35 @@ import { useContext, useEffect, useState } from "react";
|
||||
import { DomainContext, featureEnabled } from "../domainContext";
|
||||
import { OpenApi, DefaultService } from "../api";
|
||||
import { CustomSession } from "./types";
|
||||
import useSessionStatus from "./useSessionStatus";
|
||||
import useSessionAccessToken from "./useSessionAccessToken";
|
||||
|
||||
export default function useApi(): DefaultService | null {
|
||||
const api_url = useContext(DomainContext).api_url;
|
||||
const [api, setApi] = useState<OpenApi | null>(null);
|
||||
const { data: session, status } = useSession();
|
||||
const customSession = session as CustomSession;
|
||||
const accessToken = customSession?.accessToken;
|
||||
const { isLoading, isAuthenticated } = useSessionStatus();
|
||||
const { accessToken, error } = useSessionAccessToken();
|
||||
|
||||
if (!api_url) throw new Error("no API URL");
|
||||
|
||||
useEffect(() => {
|
||||
if (customSession?.error === "RefreshAccessTokenError") {
|
||||
if (error === "RefreshAccessTokenError") {
|
||||
signOut();
|
||||
}
|
||||
}, [session]);
|
||||
}, [error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === "authenticated" && !accessToken) {
|
||||
if (isLoading || (isAuthenticated && !accessToken)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const openApi = new OpenApi({
|
||||
BASE: api_url,
|
||||
TOKEN: accessToken,
|
||||
TOKEN: accessToken || undefined,
|
||||
});
|
||||
|
||||
setApi(openApi);
|
||||
}, [accessToken, status]);
|
||||
}, [isLoading, isAuthenticated, accessToken]);
|
||||
|
||||
return api?.default ?? null;
|
||||
}
|
||||
|
||||
42
www/app/lib/useSessionAccessToken.ts
Normal file
42
www/app/lib/useSessionAccessToken.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSession as useNextAuthSession } from "next-auth/react";
|
||||
import { CustomSession } from "./types";
|
||||
|
||||
export default function useSessionAccessToken() {
|
||||
const { data: session } = useNextAuthSession();
|
||||
const customSession = session as CustomSession;
|
||||
const naAccessToken = customSession?.accessToken;
|
||||
const naAccessTokenExpires = customSession?.accessTokenExpires;
|
||||
const naError = customSession?.error;
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
const [accessTokenExpires, setAccessTokenExpires] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const [error, setError] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (naAccessToken !== accessToken) {
|
||||
setAccessToken(naAccessToken);
|
||||
}
|
||||
}, [naAccessToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (naAccessTokenExpires !== accessTokenExpires) {
|
||||
setAccessTokenExpires(naAccessTokenExpires);
|
||||
}
|
||||
}, [naAccessTokenExpires]);
|
||||
|
||||
useEffect(() => {
|
||||
if (naError !== error) {
|
||||
setError(naError);
|
||||
}
|
||||
}, [naError]);
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
accessTokenExpires,
|
||||
error,
|
||||
};
|
||||
}
|
||||
22
www/app/lib/useSessionStatus.ts
Normal file
22
www/app/lib/useSessionStatus.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSession as useNextAuthSession } from "next-auth/react";
|
||||
import { Session } from "next-auth";
|
||||
|
||||
export default function useSessionStatus() {
|
||||
const { status: naStatus } = useNextAuthSession();
|
||||
const [status, setStatus] = useState("loading");
|
||||
|
||||
useEffect(() => {
|
||||
if (naStatus !== "loading" && naStatus !== status) {
|
||||
setStatus(naStatus);
|
||||
}
|
||||
}, [naStatus]);
|
||||
|
||||
return {
|
||||
status,
|
||||
isLoading: status === "loading",
|
||||
isAuthenticated: status === "authenticated",
|
||||
};
|
||||
}
|
||||
33
www/app/lib/useSessionUser.ts
Normal file
33
www/app/lib/useSessionUser.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSession as useNextAuthSession } from "next-auth/react";
|
||||
import { Session } from "next-auth";
|
||||
|
||||
// user type with id, name, email
|
||||
export interface User {
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
email?: string | null;
|
||||
}
|
||||
|
||||
export default function useSessionUser() {
|
||||
const { data: session } = useNextAuthSession();
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!session?.user) {
|
||||
setUser(null);
|
||||
return;
|
||||
}
|
||||
if (JSON.stringify(session.user) !== JSON.stringify(user)) {
|
||||
setUser(session.user);
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
return {
|
||||
id: user?.id,
|
||||
name: user?.name,
|
||||
email: user?.email,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user