diff --git a/frontend/src/contexts/auth.context.tsx b/frontend/src/contexts/auth.context.tsx index a2faf186..a3d27ace 100644 --- a/frontend/src/contexts/auth.context.tsx +++ b/frontend/src/contexts/auth.context.tsx @@ -21,6 +21,11 @@ import { Progress } from "@/app-components/displays/Progress"; import { useLogout } from "@/hooks/entities/auth-hooks"; import { useApiClient } from "@/hooks/useApiClient"; import { CURRENT_USER_KEY, PUBLIC_PATHS } from "@/hooks/useAuth"; +import { + EBCEvent, + ETabMode, + useBroadcastChannel, +} from "@/hooks/useBroadcastChannel"; import { useTranslate } from "@/hooks/useTranslate"; import { RouterType } from "@/services/types"; import { IUser } from "@/types/user.types"; @@ -59,6 +64,9 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => { i18n.changeLanguage(lang); }; const { mutate: logoutSession } = useLogout(); + const { mode, value } = useBroadcastChannel({ + channelName: "websocket-session", + }); const logout = async () => { updateLanguage(publicRuntimeConfig.lang.default); logoutSession(); @@ -107,6 +115,12 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => { setIsReady(true); }, []); + useEffect(() => { + if (value === EBCEvent.LOGOUT_END_SESSION && mode === ETabMode.SECONDARY) { + router.reload(); + } + }, [value, mode, router]); + if (!isReady || isLoading) return ; return ( diff --git a/frontend/src/hooks/entities/auth-hooks.ts b/frontend/src/hooks/entities/auth-hooks.ts index e3a1b07b..b655b3b7 100755 --- a/frontend/src/hooks/entities/auth-hooks.ts +++ b/frontend/src/hooks/entities/auth-hooks.ts @@ -22,6 +22,7 @@ import { useSocket } from "@/websocket/socket-hooks"; import { useFind } from "../crud/useFind"; import { useApiClient } from "../useApiClient"; import { CURRENT_USER_KEY, useAuth, useLogoutRedirection } from "../useAuth"; +import { EBCEvent, useBroadcastChannel } from "../useBroadcastChannel"; import { useToast } from "../useToast"; import { useTranslate } from "../useTranslate"; @@ -58,6 +59,9 @@ export const useLogout = ( const { logoutRedirection } = useLogoutRedirection(); const { toast } = useToast(); const { t } = useTranslate(); + const { send } = useBroadcastChannel({ + channelName: "websocket-session", + }); return useMutation({ ...options, @@ -67,6 +71,7 @@ export const useLogout = ( return await apiClient.logout(); }, onSuccess: async () => { + send(EBCEvent.LOGOUT_END_SESSION); queryClient.removeQueries([CURRENT_USER_KEY]); await logoutRedirection(); toast.success(t("message.logout_success")); diff --git a/frontend/src/hooks/useBroadcastChannel.ts b/frontend/src/hooks/useBroadcastChannel.ts new file mode 100644 index 00000000..0ec4f1a3 --- /dev/null +++ b/frontend/src/hooks/useBroadcastChannel.ts @@ -0,0 +1,55 @@ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { useEffect, useMemo, useState } from "react"; + +export enum ETabMode { + PRIMARY = "primary", + SECONDARY = "secondary", +} + +export enum EBCEvent { + LOGOUT_END_SESSION = "logout-end-session", +} + +export const useBroadcastChannel = ({ + channelName, + initialValue, +}: { + channelName: string; + initialValue?: EBCEvent; +}) => { + const [value, setValue] = useState(initialValue); + const [mode, setMode] = useState(ETabMode.PRIMARY); + const channel = useMemo( + () => new BroadcastChannel(channelName), + [channelName], + ); + + useEffect(() => { + channel.onmessage = (event) => { + if (mode === ETabMode.PRIMARY) { + setValue(event.data); + setMode(ETabMode.SECONDARY); + } + }; + + channel.postMessage(initialValue); + + return () => { + channel.close(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [channel, channelName, initialValue]); + + const send = (data?: EBCEvent) => { + channel?.postMessage(data); + }; + + return { mode, value, send }; +};